Related
I'm creating a quiz that requires every answer to be answered. The problem is that you should be able to skip questions if you don't have an answer. I'm trying to set a default answer everytime I press next so when I try to skip one I don't have to answer for it to have a value. The default value I want is each time the last value of my array.
next and previous question
SetQuestion(question) {
if (this.questionNumber >= 0) {
let oldAnswerButton = document.querySelectorAll('.filter_anwser');
// Deletes old question when the next question is clicked
for (let answerButton of oldAnswerButton) {
answerButton.style.display = 'none';
}
}
this.questionNumber = question;
let q = this.quiz[question];
// Check if your at the last question so the next button will stop being displayed.
if (this.questionNumber === Quiz.length - 1) {
this.nextbtn.style.display = 'none';
this.prevbtn.style.display = 'block';
this.resultbtn.style.display = 'grid';
} else if (this.questionNumber === 0) {
this.nextbtn.style.display = 'block';
this.prevbtn.style.display = 'none';
this.resultbtn.style.display = 'none';
} else {
this.nextbtn.style.display = 'block';
this.prevbtn.style.display = 'block';
this.resultbtn.style.display = 'none';
}
// Displays Question
this.questionName.textContent = q.questionText;
this.questionName.id = "questionID";
return q;
console.log(this.getLink())
console.log(this.tmp)
}
IntoArray() {
const UrlVar = new URLSearchParams(this.getLink())
this.UrlArray = [...UrlVar.entries()].map(([key, values]) => (
{[key]: values.split(",")}
)
);
}
NextQuestion() {
// let quizUrl = this.url[this.questionNumber];
let question = this.SetQuestion(this.questionNumber + 1);
let pre = question.prefix;
let prefixEqual = pre.replace('=', '');
let UrlArr = this.UrlArray;
let UrlKeys = UrlArr.flatMap(Object.keys)
let answers = question.chosenAnswer.slice(0, -1);
this.clicked = true;
// Displays answers of the questions
for (let y = 0; y < answers.length; y++) {
let item = answers[y];
// Display answer buttons
if (UrlKeys.includes(prefixEqual)) {
console.log("exists");
let btn = document.querySelector('button[value="' + item.id + '"]');
btn.style.display = 'block';
} else {
let btn = document.createElement('button');
btn.value = item.id;
btn.classList.add("filter_anwser", pre)
btn.id = 'answerbtn';
btn.textContent = item.name;
this.button.appendChild(btn);
}
// let quizUrl = control.url[control.questionNumber];
// // console.log(this.tmp);
// if (quizUrl === undefined) {
// quizUrl.push(question.prefix[y] + '');
// }
// if (quizUrl === undefined){
// this.tmp.push('');
// }
}
this.IntoArray();
}
PrevQuestion() {
let question = this.SetQuestion(this.questionNumber - 1);
let answers = question.chosenAnswer.slice(0, -1);
// Displays answers of the questions
for (let y = 0; y < answers.length; y++) {
let item = answers[y];
// Display answer buttons
let btn = document.querySelector('button[value="' + item.id + '"]');
btn.style.display = 'block';
}
this.IntoArray();
}
Link creator:
/**
* Returns the parameters for the URL.
*
* #returns {string}
*/
getLink() {
this.tmp = [];
for (let i = 0; i < this.url.length; i++) {
// Check if question is from the same quiz part and adds a , between chosen answers and add the right prefix at the beginning
if (this.url[i].length > 0) {
this.tmp.push("" + Quiz[i].prefix + this.url[i].join(","))
// console.log(this.url)
}
if (this.url[i].length === 0) {
this.tmp.push("");
}
}
/// If answers are from different quiz parts add a & between answers.
return "" + this.tmp.join("&");
// console.log(this.url[i].prefix);
};
Answer click event
control.button.addEventListener("click", function (e) {
const tgt = e.target;
control.clicked = true;
// clear the url array if there's nothing clicked
if (control.url.length === control.questionNumber) {
control.url.push([]);
}
let quizUrl = control.url[control.questionNumber];
// Check if a button is clicked. Changes color and adds value to the url array.
if (quizUrl.indexOf(tgt.value) === -1) {
quizUrl.push(tgt.value);
e.target.style.backgroundColor = "orange";
// Check if a button is clicked again. If clicked again changes color back and deletes value in the url array.
} else {
quizUrl.splice(quizUrl.indexOf(tgt.value), 1);
e.target.style.backgroundColor = "white";
}
console.log(control.getLink());
console.log(quizUrl)
})
array:
class QuizPart {
constructor(questionText, chosenAnswer, prefix, questionDescription) {
this.questionText = questionText;
this.chosenAnswer = chosenAnswer;
this.prefix = prefix;
this.questionDescription = questionDescription;
}
}
class ChosenAnswer {
constructor(id, name) {
this.id = id;
this.name = name;
}
}
let Quiz = [
new QuizPart('Whats your size?', [
new ChosenAnswer('6595', '41'),
new ChosenAnswer('6598', '42'),
new ChosenAnswer('6601', '43'),
new ChosenAnswer('', ''),
], 'bd_shoe_size_ids=',
'The size of your shoes is very important. If you have the wrong size, they wont fit.'),
new QuizPart('What color would you like?', [
new ChosenAnswer('6053', 'Red'),
new ChosenAnswer('6044', 'Blue'),
new ChosenAnswer('6056', 'Yellow'),
new ChosenAnswer('6048', 'Green'),
new ChosenAnswer('', ''),
], 'color_ids=',
'Color isn t that important, It looks good tho.'),
new QuizPart('What brand would you like?', [
new ChosenAnswer('5805', 'Adidas'),
new ChosenAnswer('5866', 'Nike'),
new ChosenAnswer('5875', 'Puma'),
new ChosenAnswer('', ''),
], 'manufacturer_ids=',
'Brand is less important. Its just your own preference'),
]
I tried giving the array's in link creator and my eventlistener a default value and replacing it when I get and actual value from one of my buttons, but it just doesn't work. Can anybody help me?
I understand, that it might be a bit far from what you expect for an answer - but why don't you have a look at a reactive tool, like Vue? It has all the tools that you might need for such a task, and maybe more:
the whole quiz can be abstracted to a simple array of objects (the questions)
next, prev, set default answer becomes a breeze
easy to extend (with questions)
simple to update (template, features, etc.)
Vue.component('QuizQuestion', {
props: ['data', 'selected'],
computed: {
valSelected: {
get() {
return this.selected
},
set(val) {
this.$emit('update:selected', val)
}
},
},
template: `
<div>
{{ data.text }}<br />
{{ data.description }}<br />
<div class="quiz-options">
<label
v-for="val in data.options"
:key="val[0]"
>
<input
type="radio"
:name="data.text"
:value="val"
v-model="valSelected"
/>
{{ val[1] }}
</label>
</div>
</div>
`
})
new Vue({
el: "#app",
computed: {
currentQuestion() {
return this.questions[this.current]
},
hasPrev() {
return !!this.current
},
hasNext() {
return this.current < this.questions.length - 1
},
},
data() {
return {
current: 0,
questions: [{
text: 'Whats your size?',
description: 'The size of your shoes is very important. If you have the wrong size, they wont fit.',
options: [
['6595', '41'],
['6598', '42'],
['6601', '43'],
['', ''],
],
selected: null,
}, {
text: 'What color would you like?',
description: 'Color isn\'t that important, It looks good tho.',
options: [
['6053', 'Red'],
['6044', 'Blue'],
['6056', 'Yellow'],
['6048', 'Green'],
['', ''],
],
selected: null,
}, {
text: 'What brand would you like?',
description: 'Brand is less important. Its just your own preference',
options: [
['5805', 'Adidas'],
['5866', 'Nike'],
['5875', 'Puma'],
['', ''],
],
selected: null,
}, ],
}
},
methods: {
selectDefault() {
this.questions[this.current] = {
...this.questions[this.current],
selected: this.questions[this.current].options.slice(-1)[0],
}
},
getPrev() {
if (this.hasPrev) {
if (!this.currentQuestion.selected) {
this.selectDefault()
}
this.current -= 1
}
},
getNext() {
if (this.hasNext) {
if (!this.currentQuestion.selected) {
this.selectDefault()
}
this.current += 1
}
},
},
template: `
<div>
<quiz-question
:data="currentQuestion"
:selected.sync="currentQuestion.selected"
/><br />
<button v-if="hasPrev" #click="getPrev">PREV</button>
<button v-if="hasNext" #click="getNext">NEXT</button>
<button v-if="!hasNext">RESULT</button>
</div>
`
})
.quiz-options {
display: flex;
flex-direction: column;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
EDIT
But, if frameworks/libraries are not to be used, here's a more OOP approach:
class Quiz {
constructor(questions) {
this._current = 0
this._questions = questions
}
get current() {
return this._current
}
set current(val) {
this._current = val
}
get hasNext() {
return this.current < this.questions.length - 1
}
get hasPrev() {
return !!this.current
}
get questions() {
return this._questions
}
get next() {
this.current = this.hasNext ? this.current + 1 : this.current
return this.currentQuestion
}
get prev() {
this.current = this.hasPrev ? this.current - 1 : this.current
return this.currentQuestion
}
get currentQuestion() {
return this.questions[this.current]
}
}
class Question {
constructor({
text,
description,
options,
prefix,
}) {
this.text = text
this.desc = description
this.prefix = prefix
this._options = options.map(([key, val]) => ({
id: key,
value: [key, val],
selected: false,
}))
}
get options() {
return this._options
}
set options(newOptions) {
this._options = newOptions
}
get selected() {
return this.options.find(({
selected
}) => !!selected)
}
set selected(selectedVal) {
this.options = [...this.options.map(({
value: [key, val],
selected,
...rest
}) => {
return {
...rest,
value: [key, val],
selected: key === selectedVal
}
})]
}
get lastOption() {
return this.options.slice(-1)[0]
}
setDefault() {
if (!this.selected) {
this.selected = this.lastOption.id
}
}
}
const urlParser = (quiz) => {
return quiz.questions.map(({
prefix,
selected = {
value: ['']
}
}) => {
const s = selected.value[0] ? selected.value[0] : ''
return `${prefix}${s}`
}).join('&')
}
const qArr = [{
text: 'text1',
description: 'desc1',
options: [
['1_1', '11'],
['1_2', '12'],
['1_3', '13'],
],
prefix: 'prefix_1_',
},
{
text: 'text2',
description: 'desc2',
options: [
['2_1', '21'],
['2_2', '22'],
['2_3', '23'],
],
prefix: 'prefix_2_',
},
{
text: 'text3',
description: 'desc3',
options: [
['3_1', '31'],
['3_2', '32'],
['3_3', '33'],
],
prefix: 'prefix_3_',
},
]
const getOptionsHtml = ({
text,
options
}) => {
let html = ''
options.forEach(({
id,
value,
selected
}, i) => {
html += `
<label>
<input
class="question-input"
type="radio"
name="${text}"
value="${value[0]}"
${selected ? 'checked' : ''}
/>
${value[1]}
</label>
`
})
return html
}
const getSingleQuestionHtml = (q) => {
const optionsHtml = getOptionsHtml({
text: q.text,
options: q.options
})
return `
${q.text}<br />
${q.desc}<br />
${optionsHtml}
`
}
const registerEventHandlers = ({
container,
question
}) => {
const radioBtns = document.querySelectorAll('.question-input')
radioBtns.forEach((input, i) => {
input.addEventListener('change', function(e) {
question.selected = e.target.value
})
})
}
const updateHtml = ({
container,
question
}) => {
container.innerHTML = getSingleQuestionHtml(question)
registerEventHandlers({
container,
question
})
};
const updateContainer = (container) => (question) => updateHtml({
container,
question
});
const setElDisplay = ({
el,
display
}) => {
if (display) {
el.classList.add("d-inline-block")
el.classList.remove("d-none")
} else {
el.classList.remove("d-inline-block")
el.classList.add("d-none")
}
}
const updateBtnVisibility = ({
btnNext,
btnPrev,
btnResult,
quiz
}) => () => {
setElDisplay({
el: btnNext,
display: quiz.hasNext
})
setElDisplay({
el: btnResult,
display: !quiz.hasNext
})
setElDisplay({
el: btnPrev,
display: quiz.hasPrev
})
}
(function() {
const quiz = new Quiz(qArr.map(q => new Question(q)))
const container = document.getElementById('quiz-container')
updateQuizContainer = updateContainer(container)
updateQuizContainer(quiz.currentQuestion)
const btnPrev = document.getElementById('btn-prev')
const btnNext = document.getElementById('btn-next')
const btnResult = document.getElementById('btn-result')
const updateBtns = updateBtnVisibility({
btnPrev,
btnNext,
btnResult,
quiz
})
updateBtns()
btnPrev.addEventListener('click', function() {
if (quiz.hasPrev) {
quiz.currentQuestion.setDefault()
}
updateQuizContainer(quiz.prev)
updateBtns()
})
btnNext.addEventListener('click', () => {
if (quiz.hasNext) {
quiz.currentQuestion.setDefault()
}
updateQuizContainer(quiz.next)
updateBtns()
})
btnResult.addEventListener('click', function() {
quiz.currentQuestion.setDefault()
console.log(urlParser(quiz))
})
})();
.d-inline-block {
display: inline-block;
}
.d-none {
display: none;
}
<div id="quiz-container"></div>
<div id="quiz-controls">
<button id="btn-prev" class="d-inline-block">
PREV
</button>
<button id="btn-next" class="d-inline-block">
NEXT
</button>
<button id="btn-result" class="d-none">
RESULT
</button>
</div>
<div id="result"></div>
I can get to limitfrom in the second case if I rename limitfrom to limitfrom2, but I don't understand how to get to the value without renaming the key. Maybe I'm doing the wrong thing about the destructuring itself.
create an object
const vedPlus = {
transferLegal: {
stageOne: {
limitfrom: 0,
limitUpTo: 60,
commission: 0
},
stageTwo: {
limitfrom2: 60,
limitUpTo2: 1000,
commission2: 29
},
}
then I do the destructuring in 4 steps.
const {transferLegal} = vedPlus
const {stageOne, stageTwo} = transferLegal
const {limitfrom, limitUpTo, commission} = stageOne
const {limitfrom2, limitUpTo2, commission2} = stageTwo
Create a function
function calc (i) {
if (i >= limitfrom && i <= limitUpTo) {
return i * commission
} else if (i >= limitfrom2 && i <= limitUpTo2) {
return i * commission2
} else {
console.log('error')
}
result
const price = 71
console.log(calc(price))
Is there a way to get to the value without renaming the key?
You can use the same property names for both inner objects, and then assign new unique names when destructuring to prevent conflicts, using const { prop: newName } = source syntax.
It may not produce the most readable code, but at least it can be done.
Demo:
const vedPlus = {
transferLegal: {
stageOne: { limitfrom: 0, limitUpTo: 60, commission: 0 },
stageTwo: { limitfrom: 60, limitUpTo: 1000, commission: 29 },
}
}
const { transferLegal } = vedPlus;
const { stageOne, stageTwo } = transferLegal;
const { limitfrom, limitUpTo, commission } = stageOne;
const { limitfrom: limitfrom2, limitUpTo: limitUpTo2, commission: commission2 } = stageTwo;
console.log(limitfrom, limitUpTo, commission);
console.log(limitfrom2, limitUpTo2, commission2);
Yes. But certainly not like this:
const {limitfrom, limitUpTo, commission} = stageOne;
const {limitfrom, limitUpTo, commission} = stageTwo;
Because you'd be re-declaring the same variables in the same scope. After these two lines, if you were to refer to the variable limitfrom, which one would you expect it to be and why?
If the keys have the same name, that's fine as long as they are unique within their context. In this case within their individual objects. You can use those objects to reference them:
const {stageOne, stageTwo} = transferLegal;
//...
if (i >= stageOne.limitfrom && i <= stageOne.limitUpTo) {
return i * stageOne.commission
} else if (i >= stageTwo.limitfrom && i <= stageTwo.limitUpTo) {
return i * stageTwo.commission
}
Basically, absolutely everything doesn't have to be destructured into its own variable with a single value. Objects exist for a reason, use them.
If you want to keep the data (vedPlus) as the same structure you can do the renaming while you are destructuring
const vedPlus = {
transferLegal: {
stageOne: {
limitfrom: 0,
limitUpTo: 60,
commission: 0
},
stageTwo: {
limitfrom: 60,
limitUpTo: 1000,
commission: 29
}
}
}
const {
transferLegal: {
stageOne: {
limitfrom,
limitUpTo,
commission
},
stageTwo: {
limitfrom: limitfrom2,
limitUpTo: limitUpTo2,
commission: commission2
}
}
} = vedPlus
function calc(i) {
if (i >= limitfrom && i <= limitUpTo) {
return i * commission
} else if (i >= limitfrom2 && i <= limitUpTo2) {
return i * commission2
} else {
console.log('error')
}
}
const price = 71
console.log(calc(price))
if the final goal is a simple calculation, then the use of the destructuring mechanism does not bring anything useful
const vedPlus =
{ transferLegal:
{ stageOne: { limitfrom: 0, limitUpTo: 60, commission: 0 }
, stageTwo: { limitfrom: 60, limitUpTo: 1000, commission: 29 }
} }
function calc(i)
{
let obj = vedPlus.transferLegal
for (let stage in obj)
{
if (obj[stage].limitfrom <= i && i <= obj[stage].limitUpTo)
return obj[stage].commission *i
}
throw `error: ${i} is out of range!`
}
console.log( '50 =>', calc(50) )
console.log( '500 =>', calc(500) )
console.log( '5000 =>', calc(5000) )
I have a JSON file from an external source containing a bunch of conditions I'd like to test. Either in realtime, or by somehow converting everything.
Let's say I have an instance of my class Person, containing {age: 13, country: "Norway"}, and that I have an external JSON file containing the following "helpers":
{
"is_child": "age < 16",
"is_adult": "age >= 16 and age < 67",
"is_senior": "age > 67",
"is_scandinavian": "country == 'Norway' or country == 'Sweden' or country == 'Denmark'",
}
and another file containing, for example, tickets I'd like to present, for example, "NorwegianTickets.json"
{
"childTicket": "is_child and is_scandinavian",
"flexTicket": "is_scandinavian and (is_adult or is_senior)"
}
How can I apply this logic to my code? If I want to run the condition "flexTicket" on my "Person", how should I map all the logic? How do I translate the "stringed" conditions, such as "and"/"or", and "()"?
You can easily achieve this using the eval function that execute a string as javascript.
So the logic will be:
Get the different conditions as a javascript string (is_child, is_adult, ...)
This function replace all variables (written as a string) by there value.
For that you will need to create a dictionary to list them all with the corresponding value:
const varsToReplace = {
country: 'Norway',
age: 12
}
Then you replace this variable in a given condition using the replace method. The only trick here is that you need to search for country and not country (if you not add the extra space before and after, a variable like user_country could be replaced by user_Norway). Also keep in mind that if you replace by a string you should wrapp the value in '':
const getConditionString = (condition) => {
let replacedConditon = ` ${conditions[condition]} `
Object.keys(varsToReplace).forEach((variable) => {
const re = new RegExp(` ${variable} `, 'g');
let replaceValue = ` ${varsToReplace[variable]} `
// If the value is a string we should add ''
if (typeof varsToReplace[variable] === 'string') {
replaceValue = ` '${varsToReplace[variable]}' `
}
replacedConditon = replacedConditon.replace(re, replaceValue)
})
return replacedConditon
}
Get the test as a javascript string (is_child and is_scandinavian, ...)
This function getTestString will replace all conditions key by the javascript string using the previous function:
const getTestString = (test) => {
let replacedTest = ` ${tests[test]} `
Object.keys(conditions).forEach((condition) => {
const re = new RegExp(` ${condition} `, 'g');
replacedTest = replacedTest.replace(re, ` ( ${getConditionString(condition)} ) `)
})
return replacedTest
}
Replace the different operators to be 'js valid':
const replaceOperators = (string) => {
const operators = {
or: '||',
and: '&&'
}
Object.keys(operators).forEach((operator) => {
const re = new RegExp(` ${operator} `, 'g');
string = string.replace(re, ` ${operators[operator]} `)
})
return string
}
Execute the js string using eval:
const evalTest = (test) => {
let testAsString = replaceOperators(getTestString(test))
return eval(testAsString)
}
Here is the full example:
const country = 'Norway'
const age = 12
const varsToReplace = {
country,
age
}
const conditions = {
"is_child": "age < 16",
"is_adult": "age >= 16 and age < 67",
"is_senior": "age > 67",
"is_scandinavian": "country == 'Norway' or country == 'Sweden' or country == 'Denmark'"
}
const tests = {
"childTicket": "is_child and is_scandinavian",
"flexTicket": "is_scandinavian and ( is_adult or is_senior )"
}
const getConditionString = (condition) => {
let replacedConditon = ` ${conditions[condition]} `
Object.keys(varsToReplace).forEach((variable) => {
const re = new RegExp(` ${variable} `, 'g');
let replaceValue = ` ${varsToReplace[variable]} `
// If the value is a string we should add ''
if (typeof varsToReplace[variable] === 'string') {
replaceValue = ` '${varsToReplace[variable]}' `
}
replacedConditon = replacedConditon.replace(re, replaceValue)
})
return replacedConditon
}
const getTestString = (test) => {
let replacedTest = ` ${tests[test]} `
Object.keys(conditions).forEach((condition) => {
const re = new RegExp(` ${condition} `, 'g');
replacedTest = replacedTest.replace(re, ` ( ${getConditionString(condition)} ) `)
})
return replacedTest
}
const replaceOperators = (string) => {
const operators = {
or: '||',
and: '&&'
}
Object.keys(operators).forEach((operator) => {
const re = new RegExp(` ${operator} `, 'g');
string = string.replace(re, ` ${operators[operator]} `)
})
return string
}
const evalTest = (test) => {
let testAsString = replaceOperators(getTestString(test))
console.log(testAsString)
return eval(testAsString)
}
console.log(evalTest('childTicket'))
console.log(evalTest('flexTicket'))
I would go for creating a DSL for that purpose. It's fun. I've written one to just give you some idea about it. Beware, its not fully tested, lacks basic functionality such as array access. I believe you may find better examples in the internet.
class Node_ {
children: Node_[];
constructor() {
this.children = [];
}
addChild = (node: Node_) =>
this.children.push(node);
evaluate = (context: any): boolean | number | string => {
throw new Error('Missing implementation');
}
}
enum ExprType {
Eq = 'eq',
Gt = 'gt',
Lt = 'lt',
Gte = 'gte',
Lte = 'lte',
Get = 'get',
}
class ExprNode extends Node_ {
expr: string;
constructor(expr: string) {
super();
this.throwIfInvalidExpr(expr);
this.expr = expr.toLowerCase();
}
throwIfInvalidExpr(expr: string) {
switch (expr.toLowerCase()) {
case ExprType.Eq:
case ExprType.Gt:
case ExprType.Lt:
case ExprType.Gte:
case ExprType.Lte:
case ExprType.Get:
break;
default:
throw new Error(`Unexpected expression: ${this.expr}`);
}
}
evaluate = (context: any) => {
switch (this.expr) {
case ExprType.Get:
return this.evaluateAccess(context);
default:
return this.evaluateCmp(context);
}
}
evaluateAccess = (context: any) => {
this.throwIfInvalidAccessOperands();
const prop = this.children[0].evaluate(context) as string;
const newContext = context[prop];
const child = this.children[1];
if (child) {
return child.evaluate(newContext);
} else {
return newContext;
}
}
evaluateCmp = (context: any) => {
this.throwIfInvalidOperands();
const left = this.children[0].evaluate(context);
const right = this.children[1].evaluate(context);
switch(this.expr) {
case ExprType.Eq:
return left === right;
case ExprType.Gt:
return left > right;
case ExprType.Gte:
return left >= right;
case ExprType.Lt:
return left < right;
case ExprType.Lte:
return left <= right;
}
}
throwIfInvalidOperands = () => {
if (this.children.length !== 2) {
throw new Error(`Invalid operand count ${this.children.length}`);
}
}
throwIfInvalidAccessOperands = () => {
if (this.children.length === 0 ||
this.children.length > 2) {
throw new Error(`Invalid access operand count ${this.children.length}`);
}
}
}
class ValueNode extends Node_ {
value: string | number;
constructor(value: string, str?: boolean) {
super();
if (str) {
this.value = value as string;
} else {
const num = parseInt(value);
if (Number.isNaN(num)) {
throw new Error(`Invalid number: ${value}`);
}
this.value = num;
}
}
evaluate = (_: any) => {
return this.value;
}
}
function tokenize(value: string): Node_ {
let index = 0;
const nodeStack = [];
let token = '';
while (index < value.length) {
switch(value[index]) {
case '(':
{
const node = new ExprNode(token);
nodeStack.push(node);
token = '';
}
break;
case ')':
{
if (token) {
const node = new ValueNode(token);
nodeStack.push(node);
addToParent(nodeStack);
token = '';
}
addToParent(nodeStack);
}
break;
case "'":
case '"':
const str = consumeString(value, index);
index += str.length + 1;
token += str;
{
const node = new ValueNode(token, true);
nodeStack.push(node);
addToParent(nodeStack);
}
token = '';
break;
case ',':
if (token) {
const node = new ValueNode(token);
nodeStack.push(node);
addToParent(nodeStack);
token = '';
}
break;
case ' ':
break
default:
token += value[index];
}
index++;
}
return nodeStack[0];
}
function consumeString(value: string, index: number) {
const delimiter = value[index++];
let ret = '';
while (value[index] !== delimiter) {
ret += value[index];
index++;
}
return ret;
}
function addToParent(nodeStack: Node_[]) {
console.assert(nodeStack.length > 0);
const last = nodeStack.pop();
if (nodeStack.length > 0) {
const parent = nodeStack.pop();
parent.addChild(last);
nodeStack.push(parent);
} else {
nodeStack.push(last);
}
}
{
const ast = tokenize('EQ("origami", GET("name"))');
const context = { name: 'origami' };
const context2 = { };
console.assert(ast.evaluate(context) === true);
console.assert(ast.evaluate(context2) === false);
}
{
const ast = tokenize('EQ(5, 5)');
console.assert(ast.evaluate({}) === true);
const ast1 = tokenize('EQ("foo", "foo")');
console.assert(ast1.evaluate({}) === true);
const ast2 = tokenize('EQ("foo", "bar")');
console.assert(ast2.evaluate({}) === false);
const ast3 = tokenize('GTE(15, 10)');
console.assert(ast3.evaluate({}) === true);
}
{
const ast = tokenize('GET("info", GET("person", GET("age")))');
const context = { info: { person: { age: 21 } } };
console.assert(ast.evaluate(context) === 21);
}
{
const ast = tokenize('LTE(21, GET("info", GET("person", GET("age"))))');
const context = { info: { person: { age: 21 } } };
console.assert(ast.evaluate(context) === true);
const context2 = { info: { person: { age: 15 } } };
console.assert(ast.evaluate(context2) === false);
}
{
const ast = tokenize('EQ(GET("info", GET("person", GET("planet"))), "earth")');
const context = { info: { person: { planet: "mars" } } };
console.assert(ast.evaluate(context) === false);
}
{
const ast = tokenize('GT(GET("person1", GET("age")), GET("person2", GET("age")))');
const context = { person1: { age: 56 }, person2: { age: 21 } };
console.assert(ast.evaluate(context) === true);
const context2 = { person1: { age: 25 }, person2: { age: 44 } };
console.assert(ast.evaluate(context2) === false);
}
I omitted AND & OR expressions, but it should be clear how to add them.
In this scenario, the client should submit the data along with the constraints. For example:
{
"context": {
"person": {
"age": 44,
"planet": "saturn"
}
},
"constraints": {
"shouldFrom": "EQ('mars', GET('person', GET('planet')))",
"minimumAge": "GTE(40, GET('person', GET('planet')))"
}
}
And the receiver part takes the each constraints, tokenize them and evaluate them with given context.
I have 4 dates of 4 people,I want to bring the name of the person who added that date but when I select the day the array is traversed 4 times and in one of them it brings the name.. that is to say the array travels all the people but not only the one I want.
for example on day 17 when I select it 2 alerts come out with error the third with the name of the person and the fourth alert error.. the others are similar
View picture
The code is this,the function of interest is infoDay
constructor(props) {
super(props);
this.state = {
selected: "",
usuarios: [],
};
}
componentDidMount() {
firebase
.database()
.ref("DatosCli/")
.on("child_added", (data) => {
var datos = data.val();
/* alert(JSON.stringify(datos)); */
var usuariosTemp = this.state.usuarios;
datos.key = data.key;
//Alert.alert("prueba",""+datos.longitud)
usuariosTemp.push(datos);
this.setState({ usuarios: usuariosTemp });
});
}
cargarDatos = async () => {
var userTemp = new Array();
var data = await firebase.database().ref("/DatosCli").once("value");
data.forEach((child) => {
var user = child.val();
user.key = child.key;
userTemp.push(user);
});
this.setState({ usuarios: userTemp });
};
render() {
const markedDates = {};
this.state.usuarios.forEach((usuarioTemp) => {
markedDates[usuarioTemp.date] = {
selected: true,
disableTouchEvent: false,
selectedColor: "orange",
selectedTextColor: "red",
};
});
const infoDay = (day) => {
this.state.usuarios.forEach((usuarioTemp) => {
if (day.dateString == usuarioTemp.date) {
alert(usuarioTemp.nombre);
} else {
alert("fail");
}
});
};
return (
<View style={styles.container}>
<CalendarList markedDates={markedDates} onDayPress={infoDay} />
</View>
);
}
If you remove the else statement you'll only see the correct alerts.
const infoDay = (day) => {
this.state.usuarios.forEach((usuarioTemp) => {
if (day.dateString == usuarioTemp.date) {
alert(usuarioTemp.nombre);
}
});
};
If you want to travel trough a specific user, you must have to make a dict with the user names
For example:
let usuarioTemp = {
firstUser:{day: 17},
secondUser:{day: 19}
}
And your function could it be:
const infoDay = (day, userName) => {
if(usuarioTemp[userName].date === day){
alert(userName);
}
};
And finally, I suppose you are getting the data like this:
[{nombre:"Name", date:17}, {nombre:"Name2", date:19}]
You can use this function:
function groupBy(array, key) {
let arrayReduced = array.reduce(
(result, { [key]: k, ...rest }) => {
(result[k] = rest);
return result;
},
{}
);
return arrayReduced;
}
and you'll see the data like this:
{
"Name": {date: 17},
"Name2": {date: 19}
}
Test:
//Data example
let data = [{nombre:"Name", date:17}, {nombre:"Name2", date:19}]
//Function groupBy
function groupBy(array, key) {
let arrayReduced = array.reduce(
(result, { [key]: k, ...rest }) => {
(result[k] = rest);
return result;
},
{}
);
return arrayReduced;
}
//Usage
console.log(groupBy(data, "nombre"))
I hope it helps!
This code gives the expected result, but is there a more concise way to achieve the same result? This is simply a matter of curiosity though.
The goal is to have a map representing the total students in each school, as well as a map representing the total teachers in each school.
// Example data
const studentsMap = {
student123: {
teacher: 'teacher123'
},
student456: {
teacher: 'teacher123'
},
student789: {
teacher: 'badID'
},
student000: {}
};
const teachersMap = {
teacher123: {
school: 'school123'
},
teacher456: {
school: 'school123'
},
teacher789: {
school: 'school456'
}
};
const studentsTotalBySchool = Object.keys(studentsMap).reduce((totals, key) => {
const current = studentsMap[key];
if (!teachersMap[current.teacher] || !teachersMap[current.teacher].school) {
return totals;
}
totals[teachersMap[current.teacher].school] = (totals[teachersMap[current.teacher].school] || 0) + 1;
return totals;
}, {});
const teachersTotalBySchool = Object.keys(teachersMap).reduce((totals, key) => {
const current = teachersMap[key];
totals[current.school] = (totals[current.school] || 0) + 1;
return totals;
}, {});
Is there a way to write this more succinctly without sacrificing too much readability?
You can use Object.entries and destructuring like so:
const studentsTotalBySchool = Object.entries(studentsMap).reduce((totals, [key, { teacher }) => {
if (!teachersMap[teacher] || !teachersMap[teacher].school) return totals;
totals[teachersMap[teacher].school] = (totals[teachersMap[teacher].school] || 0) + 1;
return totals;
}, {});
const teachersTotalBySchool = Object.entries(teachersMap).reduce((totals, [key, { school }) => {
totals[school] = (totals[school] || 0) + 1;
return totals;
}, {});
this will get you the same results with much less code
let schools = {
school123: {
teacher123 : {
students: ["student123", "student456"]
},
teacher456 : {
students: ["student789"]
}
},
school456: {
teacher123 : {
students: ["student123", "student456"]
},
teacher456 : {
students: ["student789"]
}
}
};
function findTotal(school, totalOf){
let accumulated = 0;
switch(totalOf){
case "students":
for(let teachers of Object.keys(schools[school])){
accumulated += schools[school][teachers].students.length;
}
break;
case "teachers":
accumulated = Object.keys(schools[school]).length;
}
return accumulated;
}
console.log(findTotal("school123", "students"))
console.log(findTotal("school123", "teachers"))