I have a list such as this one:
games = [
{
"id": "5559cafd-6966-4465-af6f-67a784016b41",
"date": "2021-01-23 11:58:20",
"players": ["jowic42", "robot"],
"winner": None
},
...
{
"id": "80a0a0d2-059d-4539-9d53-78b3f6045943",
"date": "2021-01-24 14:23:59",
"players": ["jowic42", "robot"],
"winner": "jowic42"
}
]
and I have to make a function that returns a list summary of the games like this :
1 : 2021-01-23 11:58:20, jowic42 vs robot
2 : 2021-01-24 14:23:59, jowic42 vs robot, winner: jowic42
Here is the code I came up with:
def games_list(games):
for i in range(len(games)):
no = i + 1
date = games[i].get('date')
winner = games[i].get('winner')
player1 = games[i].get('players')[0]
player2 = games[i].get('players')[1]
return (f'{no} : {date}, {player1} vs {player2}, winner:{winner}\n')
The problem is that it only returns the first game and the date is in the form {'2021-01-23 11:58:20'} instead of just the text 2021-01-23 11:58:20. I also want the winner part of the return to not get displayed if it's a None. How could I do this?
You could use list to which data is appended in every iteration and use a conditional operator to add winner based on its value. See below code.
def games_list(games):
no = 0
results = []
for game in games:
no = no + 1
date = game['date']
winner = game['winner']
player1 = game['players'][0]
player2 = game['players'][1]
line = str(no) + ':' + date + ' ' + player1 + ' vs ' + player2 + (' winner:' + winner if winner else '') + '\n'
results.append(line)
return results
print(''.join(games_list(games)))
Output:
1:2021-01-23 11:58:20 jowic42 vs robot
2:2021-01-24 14:23:59 jowic42 vs robot winner:jowic42
Note: used #danh's code and updated it as danh's code looked more readable.
My python is rusty, but...
def games_list(games):
i = 0
results = []
for game in games:
no = i + 1
date = game['date']
winner = game['winner']
player1 = game['players'][0]
player2 = game['players'][1]
results.append((f'{no} : {date}, {player1} vs {player2}, winner:{winner}\n')
return results
Related
i run a class in a for cycle.
the code is:
graph_all = []
graph_now = graph_exc(-1)
for lin in res_str:
if lin.strip():
print(lin)
if lin[0] == 't' :
if graph_now.numbering != (-1) :
graph_all.append(graph_now)
graph_now = graph_exc(int(lin[4:-1]))
if lin[0] == 'v' :
graph_now.add_vertex(lin[2],lin[4])
if lin[0] == 'e' :
graph_now.add_edges(lin[2],lin[4],lin[6])
else:
continue
res_str is a list full of sentences.
I use graph_all to accommdate all the class.
when the first letter is 'v' or 'e',i put something in graph_now's lists.
when the first letter is 't',i put graph_now to graph_all and re-define the graph_all.but i find a problem:when i define graph_all again,the lists in class in graph_all will be nothing,too!
the code of graph_exc is :
class graph_exc:
edges_on_1 = []
edges_on_2 = []
edges_on_edges = []
vertex_on = {}
def __init__(self,numbering):
self.num_edge = 0
self.num_vertex = 0
self.numbering = numbering
self.edges_on_edges.clear()
self.edges_on_2.clear()
self.edges_on_1.clear()
self.vertex_on.clear()
def add_vertex(self,ver,ver_num):
self.vertex_on[ver] = ver_num
self.num_vertex += 1
def add_edges(self,point_1,edges,point_2):
self.edges_on_1.append(point_1)
self.edges_on_edges.append(edges)
self.edges_on_2.append(point_2)
self.num_edge += 1
the problem is : edges_on_1,edges_on_edges,edges_on_2 and vertex will be influenced.the data in here will disappear.but the num_edge and num_vertex are not.
I try to use deepcopy method to solve it.but it does not work.
I'm using .format() to autogenerate a menu. But I also need to format it as users run more tests to indicate those tests are already done.
Example test dict:
menuDict = {
"1":
{"testDataDict": "testDataDict1",
"testName": "testName1",
"testGroupName":"testGroupName1"},
"2":
{"testDataDict": "testDataDict2",
"testName": "testName2",
"testGroupName":"testGroupName2"
},
"3":
{"testDataDict": "testDataDict3",
"testName": "testName3",
"testGroupName":"testGroupName3"
},
"4":
{"testDataDict": "testDataDict4",
"testName": "testName4",
"testGroupName":"testGroupName3"
}
}
Actual code:
def menuAutoCreate(menuDict):
testGroupDict = {}
for testNum in menuDict.keys():
try:
testGroupDict[menuDict[testNum]["testGroupName"]].append(testNum)
except:
testGroupDict[menuDict[testNum]["testGroupName"]] = [testNum]
#Groups the tests under the group names
from natsort import natsorted as nt
testGroupNamesList = nt(testGroupDict.keys(), key=lambda y: y.lower())
#Naturally sorts group names so they look orderly
textDump = " "
i = 0
while i < len(testGroupNamesList):
howManyLinesEven = 0
evenList = []
howManyLinesOdd = 0
oddList = []
testGroupNameEven = testGroupNamesList[i]
textDump += "|{:44} |".format(testGroupNameEven)
howManyLinesEven = len(testGroupDict[testGroupNameEven])
evenList = nt(testGroupDict[testGroupNameEven], key=lambda y: y.lower())
#If it's an even number, it puts the menu template on the left side of the screen
if i != len(testGroupNamesList)-1:
testGroupNameOdd = testGroupNamesList[i+1]
textDump += "{:45} |".format(testGroupNameOdd) + "\n"
howManyLinesOdd = len(testGroupDict[testGroupNameOdd])
oddList = nt(testGroupDict[testGroupNameOdd], key=lambda y: y.lower())
#If it's odd, on the right side.
if i == len(testGroupNamesList)-1:
textDump += "{:45} |".format("") + "\n"
#Ensures everything is correctly whitespaced
howManyLines = max(howManyLinesEven, howManyLinesOdd)
#Checks how many lines there are, so if a group has less tests, it will have extra whitespaces
for line in range(howManyLines):
if line < howManyLinesEven:
data = {"testNum": evenList[line], "testName": menuDict[evenList[line]]["testName"]}
textDump += "|({d[testNum]}) {d[testName]:40} {{doneTests[{d[testNum]!r}]:^8}} |".format(d=data)
else:
textDump += "|{:44} |".format("")
if line < howManyLinesOdd:
data = {"testNum": oddList[line], "testName": menuDict[oddList[line]]["testName"]}
textDump += "({d[testNum]}) {d[testName]:41} {{doneTests[{d[testNum]!r}]:^8}} |".format(d=data) + "\n"
else:
textDump += "{:45} |".format("") + "\n"
#Automatically creates a menu
i += 2
print(textDump)
print("\n")
Output of this, as expected:
|testGroupName1 |testGroupName2 |
|(1) testName1 {doneTests['1']:^8} |(2) testName2 {doneTests['2']:^8} |
|testGroupName3 | |
|(3) testName3 {doneTests['3']:^8} | |
|(4) testName4 {doneTests['4']:^8} | | |
This last step will be done elsewhere, but put here for demonstration:
doneTests = {}
for testNum in menuDict.keys():
doneTests[testNum] = "(-)"
print(doneTests)
#textDump.format(**doneTests)
#This doesn't work for some reason?
textDump.format(doneTests = doneTests)
#This step will be repeated as the user does more tests, as an indicator of
which tests are completed.
The expected output would be this:
|testGroupName1 |testGroupName2 |
|(1) testName1 (-) |(2) testName2 (-) |
|testGroupName3 | |
|(3) testName3 (-) | |
|(4) testName4 (-) | | |
But here it throws a:
KeyError: "'1'"
If you remove !r from:
{{doneTests[{d[testNum]!r}]:^8}}
It throws a
KeyError: 1
instead.
I tried formatting with !s. Using lists/tuples. Adding and removing brackets. Out of ideas at this point...
Just tried your example.
I used the function sorted() instead of natsorted() and added the line
textDump = ''
to initialize the textDump variable before the line
i = 0
As a result I got no errors and got the expected output.
EDIT
Now I reproduced your error. I removed !r from {{doneTests[{d[testNum]!r}]:^8}} and used integer keys in doneTests variable
doneTests[int(testNum)] = "(-)"
to solve the problem. I guess the origin of the problem is how format() method works.
I have a JSON array (list of maps) similar to:
def listOfMap = [[TESTCASE:1, METHOD:'CLICK', RESULT:'PASS'],
[TESTCASE:2, METHOD:'CLICK', RESULT:'FAIL'],
[TESTCASE:3, METHOD:'CLICK', RESULT:'FAIL'],
[TESTCASE:4, METHOD:'TYPETEXT', RESULT:'FAIL']]
I am grouping by the METHOD names and collecting the FAILURE % of each method
def percentage (map){
(map.FAIL ?: 0) / ((map.PASS ?: 0) + (map.FAIL ?: 0)) * 100
}
def result = listOfMap.groupBy{it.METHOD}
.collectEntries{[(it.key) : percentage(it.value.countBy{it.RESULT})]}
Now my output will be [CLICK : 66.6, TYPETEXT : 100]
To sort the above result in descending order of percentage,
def sortedResult = result.sort { a, b -> b.value <=> a.value }
Now my output will be [TYPETEXT : 100, CLICK : 66.6]
How can i get the FAIL count and PASS count of the METHOD linked to the above sorted order?
My output should be two separate lists (sorted in descending order of failure %)
passList = [0, 1] Note : [TYPETEXT passed 0 times, CLICK passed 1 time]
failList = [1, 2] Note : [TYPETEXT failed 1 time, CLICK failed 2 times]
Basically i am looking for this data to create a CSV report something like the below from the listofMap given:
Given this (from the original post):
def listOfMap = [[TESTCASE:1, METHOD:'CLICK', RESULT:'PASS'],
[TESTCASE:2, METHOD:'CLICK', RESULT:'FAIL'],
[TESTCASE:3, METHOD:'CLICK', RESULT:'FAIL'],
[TESTCASE:4, METHOD:'TYPETEXT', RESULT:'FAIL']]
Consider using Expando so that percentage is a field, but also passCount and failCount:
def percentage(passCount, failCount) {
failCount / (passCount + failCount) * 100
}
def result = listOfMap.groupBy{it.METHOD}.collectEntries{
def rec = new Expando()
def count = it.value.countBy{ it.RESULT }
rec."passCount" = count.'PASS' ?: 0
rec."failCount" = count.'FAIL' ?: 0
rec."percentage" = percentage(rec."passCount",
rec."failCount")
[(it.key) : rec]
}
def sortedResult = result.sort { a, b ->
b.value."percentage" <=> a.value."percentage"
}
sortedResult.each { println it }
The output matches the basic schema desired for the CSV:
$ groovy Example.groovy
TYPETEXT={failCount=1, percentage=100, passCount=0}
CLICK={failCount=2, percentage=66.6666666700, passCount=1}
You can do a multi-grouping to make your life easier:
def listOfMap = [[TESTCASE: 1, METHOD: 'CLICK', RESULT: 'PASS'],
[TESTCASE: 2, METHOD: 'CLICK', RESULT: 'FAIL'],
[TESTCASE: 3, METHOD: 'CLICK', RESULT: 'FAIL'],
[TESTCASE: 4, METHOD: 'TYPETEXT', RESULT: 'FAIL']]
def result = listOfMap.groupBy({ it.METHOD }, { it.RESULT })
.collectEntries { method, r ->
def passCount = r.PASS?.size() ?: 0
def failCount = r.FAIL?.size() ?: 0
[method, [passCount: passCount, failCount: failCount, failPercentage: (failCount / (passCount + failCount) * 100.0)]]
}.sort { -it.value.failPercentage }
You can then do:
result.values().failCount
or
result.values().passCount
to get the numbers you require in the question.
It's best to never try and keep multiple separate lists in the same order, much easier to store all the data together, and extract it after sorting the whole thing
I am a complete newbie and have tried solving this problem (with my own head and with online research) for the last 5 hours.
Below is a snippet of a function we have written to simulate a game. We want to offer the ooportunity to start a new round - meaning if a player hits "b", the game should start again at the beginning of the range (0, players). But right now it just goes onto the next player in the range (if player 1 enters "b", the program calls player 2)
players = input(4)
if players in range(3, 9):
for player in range(0, players):
sum_points = 0
throw_per_player_counter = 0
print("\nIt is player no.", player+1, "'s turn!\n")
print("\nPress 'return' to roll the dice.\n"
"To start a new round press 'b'.\n"
"Player", player+1)
roll_dice = input(">>> ")
if roll_dice == "b":
player = 0
throw_per_player_counter = 0
sum_points = 0
print("\n * A new round was started. * \n")
I have tried return and break, also tried to put it all in another while-loop... failed. Break and return just ended the function.
Any hints highly appreciated!
you could change the for loop to a while loop. instead of using a range, make player a counter
players = 4
if 3 <= players < 9:
player = 0 # here's where you make your counter
while player < players:
sum_points = 0
throw_per_player_counter = 0
print("\nIt is player no.", player+1, "'s turn!\n")
print("\nPress 'return' to roll the dice.\n"
"To start a new round press 'b'.\n"
"Player", player+1)
roll_dice = input(">>> ")
player += 1 # increment it
if roll_dice == "b":
player = 0 # now your reset should work
throw_per_player_counter = 0
sum_points = 0
print("\n * A new round was started. * \n")
I have a categorized view, the first column is categorized and ascending.The last column calculates the total. Imagine the view name is called view1 and it looks like the following:
1st column(categorized) | 2nd column | 3rd column (total)
Australia | | 2
| Alex | 1
| Bill | 1
Brazil | | 3
| Alan | 1
| Alice | 1
| Tom | 1
Chile | | 1
| John | 1
Denmark | | 2
| Susan | 1
| Tim | 1
| | 8
I would like to retrieve the total of each categorized column, for example, the total for Australia is 2, the total for Brazil is 3. I browse on the internet and write some code but the result is not success.
Here is my first attempt: I intend to use #DbLookup, for loop and #Count.
var value = sessionScope.Value;
var lookupValue = (#DbLookup(#DbName(),"view1",value,1));
for(var a = 0; a < lookupValue.length; a++)
{
//try to display the lookupValue and the relevant total
writer.write("<tr><td>"+lookupValue[a]+"<td> "+ #Count(lookupValue[a]) + "</td></tr>");
}
When I run the code, I get the following result:
Australia 1
Australia 1
Brazil 1
Brazil 1
Brazil 1
Chile 1
Denmark 1
Denmark 1
After the first attempt, I start the second attempt: (please note, most of the code in this part I get from the internet and I just modify a little bit to suit my case)
var myView:NotesView = database.getView('view1');
var vec:NotesViewEntryCollection = myView.getAllEntries();
var viewEnt:NotesViewEntry = vec.getFirstEntry();
writer.write("<table>");
while (viewEnt != null)
{
// try to retrieve the total in the 3rd column
writer.write("<tr><td> "+ 'viewEnt.getColumnValues()[2]' +"</td></tr>");
var tmp = vec.getNextEntry(viewEnt);
viewEnt.recycle();
viewEnt = tmp;
}
writer.write("</table>");
writer.endDocument();
I run the code and the result is empty. I do not get any total of the categorized column.
I review the code from attempt 1 and attempt 2, I don't understand why I cannot get the total.
I search on the internet again and I found this similar post: Getting a summary count in a view export?
I try to apply the solution in the application:(please note, most of the code in this part I get from that post and I just add the view name in the function)
function getCategoryData(viewName, dataColumn, getChildren) {
var v = database.getView(view1); // change viewName to view1
var nav = v.createViewNav();
var ve = nav.getFirst();
var isFirst = true;
var result = "[";
while (ve) {
if (!ve.isTotal()) {
var curData = ve.getColumnValues();
if (!isFirst) {
result += ",";
}
result += "{label : \"";
result += curData[0];
result += "\", value : ";
result += curData[dataColumn];
/* for 2 level categories we fetch additional data */
if (getChildren) {
var childve = nav.getChild();
var firstChild = true;
result += ", children : [";
while (childve) {
var childData = childve.getColumnValues();
if (!firstChild) {
result += ",";
}
result += "{label : \"";
result += childData[1];
result += "\", value : ";
result += childData[dataColumn];
result += "}";
firstChild = false;
childve = nav.getNextSibling(childve);
}
result += "]"
}
result += "}";
isFirst = false;
}
ve = nav.getNextSibling(ve);
}
result += "]";
return result;
}
I run the code and I get the result like this:
[{label : "Australia", value : undefined},{label : "Brazil", value : undefined},{label : "Chile", value : undefined},{label : "Denmark", value : undefined}]
After the third attempt, I think the reason that I cannot get the total is I misunderstand the concept about how to get the total in the view.
In fact, what I would like to do is get the total of each categorized column in view1. Therefore, imagine the result will look like this:
Australia 2
Brazil 3
Chile 1
Denmark 2
Actually, I think the first attempt is very close to the result. I feel I missed something in the code (perhaps I guess in the for loop part).
How can I get the total of each categorized column in view?
Grateful for your advice on this issue please. Thank you very much.
You are on the right track with your third attempt. You definitely want to use NotesViewNavigator.
Here is a working example based on your requirements:
/*
* #param viewName Name of the view to navigate
* #param labelColumn Number of the column in the view containing the label
* Note: column count start at 0
* #param dataColumn Number of the column in the view containing
* totals. Note: column count starts at 0
* #return String representing a javascript array of objects containing label and value
*/
function getCategoryData(viewName, labelColumn, dataColumn) {
var v:NotesView = database.getView(viewName);
var nav:NotesViewNavigator = v.createViewNav();
var ve:NotesViewEntry = nav.getFirst(); // first category
var isFirst = true;
var result = "[";
while (ve) {
var curData = ve.getColumnValues();
if (curData[labelColumn] != "") { // skip the total at the bottom of the totals column
if (!isFirst) {
result += ",";
}
result += "{label : \"";
result += curData[labelColumn];
result += "\", value : ";
result += curData[dataColumn];
result += "}";
}
isFirst = false;
var tmpentry:NotesViewEntry = nav.getNextCategory();
ve.recycle();
ve = tmpentry;
}
result += "]";
nav.recycle();
v.recycle();
return result;
}
Based on the description of your view and assuming the name of your view is "countries" you would call this function like this:
getCategoryData("countries", 0, 2);
Note: I did not test this function with multiple categorized columns.