View Single Post
Old 03-17-2024, 11:33 AM   #38
chaley
Grand Sorcerer
chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.
 
Posts: 11,765
Karma: 7029857
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
template to summarize the booklist

After playing for a while I have something that works for me.

The following template processes the books displayed on the book list, in the order they are displayed. The template has two arguments: "field name" and "item count". It looks at "field name" in each book (in order). If the book has an "item value" in that field, it checks if it already has found "item count" books for that "item value". If it has then it ignores the book. If it hasn't then it adds the book to the list of books to display.
Spoiler:
Code:
python:
def evaluate(book, context):
	from collections import defaultdict
	if context.arguments is None or len(context.arguments) != 2:
		raise ValueError("This template requires two arguments, field_name and item_count")
	field_name = context.arguments[0]
	desired_count = int(context.arguments[1])

	data_view = context.db.data
	db = context.db.new_api
	# Check if we have already computed the necessary data
	book_ids = context.globals.get('book_ids', None)
	if book_ids is None:
		# The candidates are the books matched by any previous search expression
		candidates = context.globals.get('_candidates', db.all_book_ids())
		books = defaultdict(set)
		answer = set()
		# Scan the library from top to bottom, in the order being displayed. This
		# reflects what is currently shown in the GUI, such as previous searches or VLs.
		for book_id in (tr.book_id for tr in data_view):
			# Check if this book is allowed by previous search terms
			if book_id not in candidates:
				continue
			# Get the value(s) to be checked.
			item_values = db.field_for(field_name, book_id)
			if item_values is None:
				continue
			if isinstance(item_values, str):
				# We have a single value, such as a publisher or series.
				# Make it into a list of one value
				item_values = (item_values,)
			for item_val in item_values:
				# Check if we have seen this item value before.
				item_set = books[item_val]
				if len(item_set) < desired_count:
					# We haven't hit the limit of books with this item value to display
					item_set.add(book_id)
					answer.add(book_id)
		# Save the computed results to be used for the rest of the books being examined
		context.globals['book_ids'] = answer

	# Check if the current book is to be displayed
	book_ids = context.globals['book_ids']
	return '1' if book.id in book_ids else ''
Click image for larger version

Name:	Clipboard05.jpg
Views:	15
Size:	159.2 KB
ID:	206972


Because the book list is scanned in displayed order, how the book list is sorted is important. The "item values" are checked in their order of appearance. This lets you control which books you get, for example:
  • Highest or lowest series index.
  • Find series with more than 5 books then show the book with the lowest series index. (Uses this template search for the Find series part.)
  • Tags with the highest or lowest rating.
  • Publishers with highest or lowest rating.
  • Authors of books with a series that has notes, in genre order.
  • And so on.
Basically, if you can sort the book list into the order you want, this template will respect that order, showing "item count" books having "field name" with some "item value".

Although not strictly required, the template works much better if running from latest source. If you don't then search caching can get involved, showing you some previous result instead of a current calculation. The change will be in the next release/preview.

The following example will show the first book of each series that has a rating.
  • As above, create a stored template containing the template code in the spoiler. Name it whatever you want. I used "summarize_booklist". Ignore the ValueError shown in the Stored templates dialog.
  • Use search to find the list of books you want to process. I recommend you make a temporary VL for the search once you have what you want. Example: rating:true
  • Sort the book list in the order you want books to be examined. Example: series ascending.
  • Using Advanced search, make a template search. Example: lowest series index. Because I used the name "summarize_booklist", the search looks like this:
    Click image for larger version

Name:	Clipboard01.jpg
Views:	23
Size:	69.9 KB
ID:	206973

    The template is
    Code:
    program: summarize_booklist('series',1)

Last edited by chaley; 03-18-2024 at 07:24 AM. Reason: Changed the template name to summarize_booklist()
chaley is offline   Reply With Quote