products = ["Coke",23,34];
I want to print this list into proper output view, means the spaces should remain same in any compiler.
ItemName Price Cost
Coke 23 34
Something like this?
void main() {
printProducts(
header: const ['ItemName', 'Price', 'Cost'],
products: [
["Coke", 23, 34]
],
columnSpace: 12,
);
// ItemName Price Cost
// Coke 23 34
}
/// Method that prints a list of products to standout output in a table form.
/// The method takes the following three arguments:
///
/// * [header] defines the first row of the table and should be a list of names
/// for each column.
///
/// * [products] List of products (where each product is defined as a
/// List<Object> where each element is column value.
///
/// * [columnSpace] defines the space reserved for each column. This value
/// should be set to a value larger that the widest item (including header)
void printProducts({
required List<String> header,
required List<List<Object>> products,
required int columnSpace,
}) {
// StringBuffer is used so we can add more data to a line of String as we go
// through each column of data. This is needed since the "print" function will
// automatically add a newline after printing to the screen.
final stringBuffer = StringBuffer();
// Start with the header and then iterate over each product. The
// [header, ...products] syntax means that we defines a new list containing
// first "header" and then all elements (... means we add all elements from
// another iterable compatible object (e.g. list)) of "products". We are then
// iterating over this new list.
for (final row in [header, ...products]) {
// Go though each column in our row. We use a normal for-loop since we want
// to be able to check if we are at the last element.
for (var i = 0; i < row.length; i++) {
// Write our column to the StringBuffer. We checks if we are at the last
// element since if that is the case, we don't need to add the padding to
// the right.
//
// The `padRight` will check the size of the String and add padding
// (by default, it uses spaces) to make sure the String is a given length.
stringBuffer.write(
i == header.length - 1
? row[i]
: row[i].toString().padRight(columnSpace),
);
}
// Print the content of our StringBuffer (contains one line of data).
print(stringBuffer);
// Clear the StringBuffer so it does not contain any data. We can then reuse
// it for the next line of data.
stringBuffer.clear();
}
}
Related
I have a spreadsheet with criteria, a start and end date, and a value. The goal is to find the lowest value for each unique criteria and start date without overlapping dates (exclusive of end date). I made a pivot table to make it easier for myself but I know there is probably a way to highlight all valid rows that meet the above requirements with some formula or conditional formatting.
I have attached a google drive link where the spreadsheet can be found here and I have some images of the sheet as well. I know that it might be possible with conditional formatting but I just don't know how to combine everything I want it to do in a single formula.
Example below:
Row 2 is a valid entry because it has the lowest value for Item 1 starting on 03-15-2021, same with row 9.
Row 5 is valid because the start date does not fall within the date range of row 2 (exclusive of end date)
Row 7 is not valid because the start date is between the start and end date of row 6
You may add a bounded script to your project. Then you can call it either with a picture/drawing that has the function assigned (button-like), or adding a menu to Google Sheets.
From what you said in the question and the comments, this seems to do what you are trying. Notice that this requires the V8 runtime (which should be the default).
function validate() {
// Get the correct sheet
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet()
const sheet = spreadsheet.getSheetByName('Sheet1')
// Get the data
const length = sheet.getLastRow() - 1
const range = sheet.getRange(2, 1, length, 4)
const rows = range.getValues()
const data = Array.from(rows.entries(), ([index, [item, start, end, value]]) => {
/*
* Row Index
* 1 Criteria 1
* 2 Item 1 0
* 3 Item 1 1
* 4 Item 1 2
*
* row = index + 2
*/
return {
row: index + 2,
criteria: item,
start: start.getTime(),
end: end.getTime(),
value: value
}
})
// Sort the data by criteria (asc), start date (asc), value (asc) and end date (asc)
data.sort((a, b) => {
let order = a.criteria.localeCompare(b.criteria)
if (order !== 0) return order
order = a.start - b.start
if (order !== 0) return order
order = a.value - b.value
if (order !== 0) return order
order = a.end - b.end
return order
})
// Iterate elements and extract the valid ones
// Notice that because we sorted them, the first one of each criteria will always be valid
const valid = []
let currentCriteria
let currentValid = []
for (let row of data) {
if (row.criteria !== currentCriteria) {
// First of the criteria
valid.push(...currentValid) // Move the valids from the old criteria to the valid list
currentValid = [row] // The new list of valid rows is only the current one (for now)
currentCriteria = row.criteria // Set the criteria
} else {
const startDateCollision = currentValid.some(valid => {
row.start >= valid.start && row.start < valid.end
})
if (!startDateCollision) {
currentValid.push(row)
}
}
}
valid.push(...currentValid)
// Remove any old marks
sheet.getRange(2, 5, length).setValue('')
// Mark the valid rows
for (let row of valid) {
sheet.getRange(row.row, 5).setValue('Valid')
}
}
Algorithm rundown
We get the sheet that we have the data in. In this case we do it by name (remember to change it if it's not the default Sheet1)
We read the data and transform it in a more an array of objects, which for this case makes it easier to manage
We sort the data. This is similar to the transpose you made but in the code. It also forces a priority order and groups it by criteria
Iterate the rows, keeping only the valid:
We keep a list of all the valid ones (valid) and one for the current criteria only (currentValid) because we only have to check data collisions with the ones in the same criteria.
The first iteration will always enter the if block (because currentCriteria is undefined).
When changing criteria, we dump all the rows in currentValid into valid. We do the same after the loop with the last criteria
When changing criteria, the CurrentValid is an array with the current row as an element because the first row will always be valid (because of sorting)
For the other rows, we check if the starting date is between the starting and ending date of any of the valid rows for that criteria. If it's not, add it to this criteria's valid rows
We remove all the current "Valid" in the validity row and fill it out with the valids
The cornerstone of the algorithm is actually sorting the data. It allows us to not have to search for the best row, as it's always the next one. It also ensures things like that the first row of a criteria is always valid.
Learning resources
Javascript tutorial (W3Schools)
Google App Scripts
Overview of Google Apps Script
Extending Google Sheets
Custom Menus in Google Workspace
Code references
Class SpreadsheetApp
Class Sheet
Sheet.getRange (notice the 3 overloads)
let ... of (MDN)
Spread syntax (...) (MDN)
Arrow function expressions (MDN)
Array.from() (MDN)
Array.prototype.push() (MDN)
Array.prototype.sort() (MDN)
Date.prototype.getTime() (MDN)
String.prototype.localeCompare() (MDN)
I have identified 3-5 keywords for every requirement in module-A. Each keyword is separated by a comma. Now I want to search every requirement in module-B to see which of them have words that match each of the key words.
Not sure exactly what you're looking for. You might have to specify if none of these solutions I'm about to propose are exactly what you're looking for.
If you're trying to create a filter which displays only objects with those keywords in your current view, you can create an advanced filter by first going to filter (Tools > Filter > Define) and then select the Advanced button on the bottom left of the filter menu that appears.
At this point you can create custom rules for the filter. I would just create an individual rule for each word with the following definition:
Attribute: Object Text
Condition: contains
Value: <insert word here>
Match Case: uncheck
Regular Expression: uncheck
Then select the Add button to add the rule to the list of available rules in the Advanced Options.
At this point you can select multiple rules in the list of available rules and you can AND/OR these rules together to create a custom filter for the view that you want.
So that's for if you're trying to create a custom view with just objects containing specific words.
If you're talking about writing DXL code to automatically spit out requirements that have a particular word in it. You can use the something that looks like this:
Object o
String s
int offset, len
for o in entire (current Module) do
{
if (isDeleted(o)) continue
s = o."Object Text"""
if findPlainText(s, "<insert word here>", offset, len, false)
{
print identifier(o) // or replace this line with however you want to display object
}
}
Hope this is helpful. Cheers!
Edit:
To perform actions on a comma separated list, one at a time, you can use a while loop with some sort of search function that cuts off words one at a time.
void processWord (string someWord, Module mTarget, Object oSource)
{
Object oTarget
string objID = identifier(oSource)
for oTarget in mTarget do
{
if (<someWord in oTarget Object Text>) // edit function as necessary to do this
{
if (oTarget."Exists""" == "")
oTarget."Exists" = objID
else
oTarget."Exists" = oTarget."Exists" "," objID
}
}
}
int offset, len
string searchCriteria
Module mSource = read("<path of source module>", true)
Module mTarget = edit("<path of target module>", true)
Object oSource
for oSource in mSource do // can use "for object in entire(m)" for all objects
{
if (oSource != rqmt) continue // create conditions specific to your module here
searchCriteria = oSource."Search Criteria"""
while (findPlainText(searchCriteria, ",", offset, len, false))
{
processWord(searchCriteria[0:offset-1], mTarget, oSource)
searchCriteria = searchCriteria[offset+1:]
}
}
processWord(searchCriteria, mTarget, oSource) // for last value in comma separated list
Base on the resultset below, I have created a column stack chart. They have been converted to an 4-attribrute object list (called as SKUQtyMetric - all are strings except for Quantity as Integer)
When converting this to a stack chart, I could not get the second item to appear next to the first item in each of the x-ticks (X represents hour). If I am not mistaken, there should only be four DataSeries objects, each representing an outlet and then do an inner loop. Just as a side note, each outlet is designated a color even if there's more than one item (I am keeping a limit of three items to be selected for searching in database), hence there's the outletFromH code below.
The current rendering of the code (using Vaadin Charts 4) is as below:
Map<String, Set<String>> myMaps = new HashMap<String, Set<String>>();
for (SkuQtyMetric item : objList) {
if (!myMaps.containsKey(item.getOutletName())) {
myMaps.put(item.getOutletName(), new HashSet<String>());
}
myMaps.get(item.getOutletName()).add(item.getItemName());
}
String asgnColor = "#ffcccc";
for(Map.Entry<String, Set<String>> map: myMaps.entrySet()) {
DataSeries dataSeries = new DataSeries(map.getKey()+"");
PlotOptionsColumn plotOptions = new PlotOptionsColumn();
plotOptions.setStacking(Stacking.NORMAL);
DataLabels labels = new DataLabels(true);
Style style = new Style();
style.setFontSize("9px");
style.setTextShadow("0 0 3px black");
labels.setStyle(style);
labels.setColor(new SolidColor("white"));
plotOptions.setDataLabels(labels);
ls.add(dataSeries);
for(String itemName: map.getValue()) {
System.out.println("Inside " + map.getKey() + ", value is: " + itemName);
dataSeries.setId(itemName);
for(SkuQtyMetric metric : objList) {
for (Map.Entry<String, String> outletfromH : Constant.SYCARDA_COLOR.entrySet()) {
if (outletfromH.getKey().equalsIgnoreCase(map.getKey())) {
asgnColor = outletfromH.getValue();
}
}
System.out.println("DataSeries Id: " + dataSeries.getId() + " , Item metric name is: "+metric.getItemName());
if(dataSeries.getName().equalsIgnoreCase(metric.getOutletName())) {
if(dataSeries.getId().equalsIgnoreCase(metric.getItemName())) {
DataSeriesItem dataSeriesItem = new DataSeriesItem(xFor(metric.getHourNumber()), metric.getQuantityAmt());
dataSeriesItem.setId(metric.getItemName()+"_setSeriesId");
dataSeriesItem.setColor(new SolidColor(asgnColor));
dataSeries.setStack(metric.getItemName());
plotOptions.setColor(new SolidColor(asgnColor));
dataSeries.setPlotOptions(plotOptions);
dataSeries.add(dataSeriesItem);
}
}
}
}
}
for (int j = 0; j < ls.size(); j++) {
listSeries.add(ls.get(j));
}
chart.getConfiguration().getSubTitle().setText(subpageName);
chart.getConfiguration().setSeries(listSeries);
When rendered, I could only get this result:
That's the current chart but second item (coconut water) is missing, shown with red scribbling. What I am not sure is whether my Map class OR my list objects controlled isn't correct OR it might be that there should be eight not four DataSeries (each being a outlet and a product). If not, is there a much more efficient way to handle the code to render the chart than what I am doing right now?
It should be possible to have multiple DataSeriesItem in the same series that go in the same stack. Four series should be enough, no need to have 8.
You can do it by setting the name of the DataSeriesItem and set the XAxis type to category so that the categories are computed from the point names.
Or by setting numeric X values to the DataSeriesItem and setting the categories to the XAxis as a String[].
Having separate series makes some things easier, like tooltips, and showing/hiding data from the legend. But in theory everything can be in a single series.
In some cases you might need to be sure that data is sorted by X value, or by name if you use the name as categories otherwise some points might not show correctly, you can check if that's your case by checking the browser console for error or warnings about that.
In my application I have to show only the top 5 items of a long list that have the highest rating. I have implemented this as follows:
The long list is an array that I have turned into an observable array with all elements as observables with ko.mapping, and the top 5 items are a computed array that depends on the long list. Whenever anything in the long list changes the computed array resorts the long list and takes the top 5 items.
My problem is that the long array with ko.mapping takes up 60MB of memory, whereas without ko.mapping it only takes 4MB. Is there a way to achieve this effect without ko.mapping the long array?
Here is a fiddle in which I have recreated the scenario with a smaller and simpler long array, but it's just for you to understand what I'm talking about.
This is the long array, for the demo I've made it 12 elements long:
this.longArray = ko.mapping.fromJS([
{name:"Annabelle"},
{name:"Vertie"},
{name:"Charles"},
{name:"John"},
{name:"AB"},
{name:"AC"},
{name:"AD"},
{name:"AE"},
{name:"AF"},
{name:"AG"},
{name:"AH"},
{name:"AJ"}
]);
And this is the computed array(showing only the top 5):
this.sortedTopItems = ko.computed(function() {
return self.longArray().sort(function(a, b) {
if(a.name() < b.name()) return -1;
if(a.name() > b.name()) return 1;
return 0;
}).slice(0, 5);
}, this);
The change one button is to simulate the long array changing and the reset button is to reset the array to its initial state.
You sure can, but the simplest way would be to filter the data before putting into knockout. If you only ever care about the first 5. Let's assume your long array of items is called data. Note that I'm not able to test this right now, but it should give you a good idea.
const sortedTopItems = ko.observableArray([]);
// Call with new data
const update = (data) => {
data.sort((a,b) => a.name - b.name);
sortedTopItems(data.slice(0, 5));
}
This handles the case for simple data where it's not observable. If you want the actual data items (rows) to be observable then I'd do the following:
const length = 5;
// Create an empty array and initialize as the observable
const startData = new Array(length).map(a => ({}));
const sortedTopItems = ko.observableArray(startData);
// Call with new data
const update = (data) => {
data.sort((a,b) => a.name - b.name);
for(let i = 0; i < length; i++) {
const item = sortedTopItems()[i];
ko.mapping.fromJS(data[i], item); // Updates the viewModel from data
}
}
I have vaadin table with multiple columns. One of the column is of label class
container.addContainerProperty(ID_COLUMN, Label.class, "");
and I fill it up like
referenceItem.getItemProperty(ID_COLUMN).setValue(new Label(new Integer(reference.getId()).toString()));
When I sort the table by clicking on this table, it sorts the data like
1
10
100
2
200
7
So, I tried to change the class to Integer, then it works fine, but I get the numbers with comma like
1
2
...
..
7,456
8,455
How can I have the data sorted numerically and no commas.
I was able to figure out. I used Integer as class for my column and used following
referenceTable = new Table()
{
#Override
protected String formatPropertyValue(final Object a_row_id, final Object a_col_id, final Property<?> a_property)
{
if (a_property.getType() == Integer.class && null != a_property.getValue())
{
DecimalFormat df = (DecimalFormat) DecimalFormat.getInstance(getLocale());
df.applyLocalizedPattern("#0");
return df.format(a_property.getValue());
}
return super.formatPropertyValue(a_row_id, a_col_id, a_property);
}
};
It has been a while since i have been having fun with Vaadin Table.
There are property formatters, generators etc... stuff but in this case it might be easiest just to:
container.addContainerProperty(ID_COLUMN, String.class, "");
referenceItem.getItemProperty(ID_COLUMN).setValue(""+reference.getId());