Hello I am working on a project here I have a multidimensional array with name and votes this is the array
[[[Avoos, 0]], [[Abhiram MS, 1]], [[Amritha tg, 0]], [[usha, 2]]]
I get this from the server,the problem is every time I refresh the page it stores these values again in the array(repeating the same values) so its getting something like this
[[[Avoos, 0]], [[Abhiram MS, 1]], [[Amritha tg, 0]], [[usha, 2]], [[Amritha tg, 0]], [[Abhiram MS, 1]], [[Avoos, 0]], [[usha, 2]], [[Amritha tg, 0]], [[Abhiram MS, 1]], [[Avoos, 0]], [[usha, 2]]]
I want to remove these repeated values and sort this array according to the votes
here is my complete code used on the page
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:web3dart/web3dart.dart';
import '../../services/Auth.dart';
import '../../services/IntoLogin.dart';
import '../../services/functions.dart';
class CloseElec extends StatefulWidget {
final Web3Client ethClient;
final String electionName;
final String electionAdress;
const CloseElec({Key? key, required this.ethClient, required this.electionName, required this.electionAdress}) : super(key: key);
#override
State<CloseElec> createState() => _CloseElecState();
}
class _CloseElecState extends State<CloseElec> {
void refresh() {
setState(() {
//candidatearray.clear();
candidatearray =candidatearrayreal.toSet().toList();
});
}
Future<void> signOut() async {
if (!mounted) return;
await Auth().signOut();
if (!mounted) return;
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => IntroLogin()),
(route) => false);
}
late String winner = 'No candidate';
late int winnervotes = 0;
late int row = 5;
late int col = 5;
var candidatearray = [] ;
var candidatearrayreal = [] ;
#override
void initState() {
candidatearray.clear();
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(gradient:
LinearGradient(colors: [
Color(0xFF516395),
Color(0xFF614385 ),
])),
child: Scaffold(
appBar:AppBar(
backgroundColor: Colors.transparent,
leading: IconButton(onPressed: () {
signOut();
}, icon: const Icon(Icons.logout_sharp),),
title: const Text('Election progress'),
actions: [
IconButton(onPressed: () {
refresh();
}, icon: const Icon(Icons.refresh))
],
),
body: SingleChildScrollView( //Here we are getting the whole candidate details
child: Column(
children: [
Container(margin: const EdgeInsets.only(bottom: 56),
child: SingleChildScrollView( // this stream builder will give the number of items/candidates
child: StreamBuilder<List>(stream: getCandidatesNum(widget.ethClient, widget.electionAdress).asStream(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator(),);//circular bar for waiting
} else {
return Column(
children: [ // here we will get all candidates using a loop
for (int i = 0; i < snapshot.data![0].toInt(); i++)
FutureBuilder<List>( // call to get candidate info
future: candidateInfo(i, widget.ethClient, widget.electionAdress),
builder: (context, candidatesnapshot) {
if (candidatesnapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator(),);
} else {
// logic to decide the winner
if(candidatesnapshot.data![0][1].toInt() > winnervotes){
winnervotes = candidatesnapshot.data![0][1].toInt();
winner = candidatesnapshot.data![0][0];
}else if(candidatesnapshot.data![0][1].toInt() == winnervotes){
winner = candidatesnapshot.data![0][0];
}
candidatearrayreal.add(candidatesnapshot.data);
// print(candidatesnapshot.data);
return Container(
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.all(12),
decoration: const BoxDecoration(
boxShadow: [
BoxShadow(color: Color(0xFF7F5A83),
offset: Offset(-11.9, -11.9),
blurRadius: 39,
spreadRadius: 0.0,
),
BoxShadow(color: Color(0xFF7F5A83),
offset: Offset(11.9, 11.9),
blurRadius: 39,
spreadRadius: 0.0,
),
],
borderRadius: BorderRadius.all(Radius.circular(10)),
gradient: LinearGradient(colors: [
Color(0xFF74F2CE),
Color(0xFF7CFFCB),
])),
child: ListTile(
title: Text('Name: ${candidatesnapshot.data![0][0]}',
style: const TextStyle(color: Colors.purple)),
subtitle: Text('Votes: ${candidatesnapshot.data![0][1]}',
style: const TextStyle(color: Colors.purple)),
),
);
}
})
],
);
}
},
),
),
),
const SizedBox(height: 12,),
Text('The winner of the election is : $winner with votes $winnervotes',style: const TextStyle(color: Colors.white)),
const SizedBox(height: 16,),
SizedBox(
height: MediaQuery.of(context).size.height,
width: double.infinity,
child: ListView.builder(
itemCount:candidatearray.length,
itemBuilder: (context,index){
for (var i = 0; i < candidatearray.length; i++) {
candidatearray.sort((a, b) {
print(candidatearrayreal);
//print(b[0][1]);
return int.parse(a[0][1].toString()).compareTo(int.parse(b[0][1].toString()));
});
}
return ListTile(
title: Text('${candidatearray[index][0][0]}'),
subtitle:Text('votes : ${candidatearray[index][0][1]}'),
);
}),
),
],
),
),
),
);
}
}
This seems a lot like a task a model class would make 100 times easier. Instead of using a multi-dimensional array where you might make various different mistakes, you can combine the data into a single object and get IDE support too.
In your case, that class could look like this:
class MyObject {
final String name;
final int votes;
MyObject({required this.name, required this.votes});
}
When you receive the data in your FutureBuilder you will need to transform the list of lists into a list of these objects. This is already much better, but there's more!
You can implement the == and hashCode overrides which allows you to compare two of the same objects and determine whether they are equal or not. Write this in your model class too:
#override
bool operator ==(covariant MyObject other) {
if (identical(this, other)) return true;
return
other.name == name &&
other.votes == votes &;
}
#override
int get hashCode {
return name.hashCode ^
votes.hashCode;
}
Now you can compare them anywhere by simply writing ==:
MyObject a = MyObject(name: "test", votes: 10);
MyObject b = MyObject(name: "test", votes: 10);
MyObject c = MyObject(name: "xyz", votes: 0);
print(a == b); // true
print(a == c); // false
What this also allows you to do is instead of storing the returned list candidatesnapshot.data, you can create a Set, which only allows any given value to be stored inside it a single time! This automatically eliminates you from adding duplicates, if that isn't what you want.
To do this, you're going to have to fix up the code after your FutureBuilder. I'm not going to lie, I don't really understand what's going on there since you seem to be creating a new FutureBuilder for every object returned by the Stream. Either way, I think it would be best to simply loop over the values from the stream and add the data to the Set.
Now, you can also easily sort this set of your MyObjects. Simply get the values as a list and then use the lists .sort method.
Set<MyObjects> m = {...}; // your data goes here
final sortedList = m.toList()..sort((a, b) => a.votes.compareTo(b.votes));
Since the types are only int and string, you can use their compareTo methods and automatically sort them. To reverse the order, simply reverse a and b on the sort function!
If you for some reason can't use a model class - and I really don't see why you couldn't - it is still possible to filter out duplicates by either:
overwriting the list every time you get new data (using x = .. instead of x.add()
checking for duplicates by looping over the list for every new value / using the .contains() method on the list
As for sorting, you can use the .sort() method, you'll just have to use a [index] syntax instead of object fields on the right side, which really isn't great either.
To remove the repeated values and sort the array according to the votes, you can use the toSet() function and then convert it back to a list using toList(). This will remove any duplicate values. Then you can use the sort() function to sort the array according to the votes.
candidatearray = candidatearrayreal.toSet().toList();
candidatearray.sort((a, b) => a[1].compareTo(b[1]));
This will first convert the candidatearrayreal to a set, which will remove any duplicate values, and then convert it back to a list. Next, it will sort the list according to the second element of each sublist (which is the number of votes) using the sort() function and a comparator function that compares the votes of the two candidates.
You can put this piece of code in the refresh() function, so it will be executed every time the user refreshes the page.
Also, you can move the candidatearray.clear(); code to the initState() function, this will clear the array every time the user enters the CloseElec page.
Related
I'm new to React and struggling with something that would be very simple with XML/XPath.
I have two array objects. For simplicity sake, I've removed most properties to demonstrate and just set everything as strings...
customerList: Customer[]
export class Customer {
id: string = "";
firstname: string = "";
}
and then a second array object:
orderList: Order[]
export class Order {
id: string = "";
customerid: string = "";
}
Assume that the order.customerid is nullable.
What I want to do is loop through each customer, and check if there are any customers without an order.
What I've tried:
{customers.map((customer) => {
return(
orders.map((order) => {
if(order.customerid == customer.id)
{
order.customerid == customer.id ? <p>customer has order</p> : <p>customer does not have order</p>
}
})
)
)}
I figured I'd set some sort of boolean flag to indicate whether there are any customers without orders, but just wanted to get something functional.
When you use map functions, a new array is created from what's returned in each loop.
i.e.:
const arr = [1, 2, 3, 4, 5];
const newArr = arr.map((i) => i*i);
console.log(newArr)
//Output: [1, 4, 9, 16, 25]
In your case, since you have not defined what to return, it will just return undefined and hence you will get a nested array of undefined values.
In order to implement the functionality you want, I'd do something like this
const newArr = customerList.map((customer) => {
return {
...customer,
customerOrdered: orders.find(order => order.customerid === customer.id)
}
})
It will create a new array of customers indicating whether the customer has placed an order or not
You not mentioned in the question but i assume that you want to render that the customer to screen that this customer don't have any order.
So you have to RETURN it. this is common mistake when people start using React.
But look like you using typescript and use it wrong way. You don't have to create a class. Create a type or interface instead.
There are many way to archive what you want to do, this just one of them that you won't need to create new array or change state format.
interface Customer {
id: string,
name: string,
}
interface Order {
id: string,
customerId: string
}
// Pretend that you pass customer and orders to this component
function Component({customers, orders}) {
return (
{customers.map((customer) => {
let haveOrder = false;
orders.forEach((order) => {
if(order.customerid == customer.id){
haveOrder = true;
}
});
if (haveOrder){
return(
<p>customer has order</p>
)
}else {
return(
<p>customer does not have order</p>
)
}
)}
)
I'm not sure if my strategy or logic is correct to achieve this, but I have 2 lists coming from 2 different jsondata mysql queries, let's say they look like this:
List newsAgency = [{id: 1, name: 9news, image: x}
{id: 2, name: abcnews, image: y}
{id: 3, name: bbcnews, image:z}];
List following = [{userid: 41, username: x, newsid: 2}
{userid: 41, username: x newsid: 3}];
I want to see if the id in newsAgency matches the newsid in the following list, and to return true or false correspondingly.
The idea is to say that I am following 2 news agencies out of 3, so my goal is to display the button to follow or unfollow based on the results.
I tried everything suggested from this post how can I find a list contains in any element another list in flutter? but couldn't get it.
This is my code example:
Listview.builder(
itemCount: newsAgency.length,
itemBuilder: (BuildContext context, int index) {
bool following = false;
return Card(
elevation: 10.0,
child: Row(
children: [
Text(newsAgency[index]['name'],
following
? TextButton(onPressed: () {//unfollow function},
child: const Text('unfollow')),
: TextButton(onPressed: () {//follow function},
child: const Text('follow')),
]));});
Any help is highly appreciated
add this method in your class, it will be responsible for searching which items are following and which are not, and return a bool based on it:
bool checkIsFollowing(Map<String, dynamic> current) {
for(int index = 0; index < following.length; index+=1) {
if(current["id"] == following[index]["newsid"]) {
return true;
}
}
return false;
}
now inside of your ListView's itemBuilder, replace this:
bool following = false;
with this:
final currentNewsAgency = newsAgency[index];
bool following = checkIsFollowing(currentNewsAgency);
following will be true or false based on if the current Agency's id exists in some item in the following list.
use contains() method to check if the list contains the value you want to match or not, for example:-
following.contains(newsAgency[index]['name'].toString())// true if value matches.
In my code I have a dropdown that receives the values from a list but these values are within the variable value of the dropdown and I can't access the first or second value. Follow the code:
List<int> cilindro1 = [4145, 2100];
List<int> cilindro2 = [4405, 2085];
List<int> cilindro3 = [4140, 2095];
child: DropdownButton<String>(
items: [
DropdownMenuItem<String>(
child: Text('Cilindro 1'),
value: '$cilindro1',
),
DropdownMenuItem<String>(
child: Text('Cilindro 2'),
value: '${cilindro2}',
),
DropdownMenuItem<String>(
child: Text('Cilindro 3'),
value: '${cilindro3}',
),
],
onChanged: (String value) {
setState(() {
_valoresArray = value;
});
},
The variable that contains the values is _valoresArray and I can't choose the index from it.
I have solved the problem using substring.
var peso = value.substring(1, 5);
var volume = value.substring(7, 11);
_peso = peso;
_volume = volume;
So I have a dilemma.
I have the next code
const loc = [
{ location_key: [32, 22, 11], autoassign: 1 },
{ location_key: [41, 42], autoassign: 1 }
];
const bulkConfigs = [
{
dataValues: {
config_key: 100,
}
},
{
dataValues: {
config_key: 200,
}
}
];
I need to create an object looking like this:
config_key: here get the config key from from bulkConfigs,
location_key: here get the location_key,
autoassign: 1
Also I need this object created
config_key: config_key,
location_key: '',
autoassign: 1,
as many times as they are locations for each config_key, what I mean is in this example from config_key: 200 we will have 2 objects like this one and for config_key: 100 we will have 3 objects like this. I suppose this can be done with reduce ... also bulkConfigs and loc can have more then just 2 objects, but the number will be always the same, like if they are 3 bulkConfigs there will be also 3 loc, but location_key might be different, one can have 7 location_key, other 4, and the last one just 1.
So in other words, the arrys are always the same length and they are always in the same order so they have the same index. Only the location_key can change, and I need the object created as many times as location_key exist.
I have tried a few things, but I don't know when it comes to this stuff .... I just can't do, that's what happens when you start with react and not java script :)
Ok so I managed to do this using lodash, here is my solution, I know it's nested like hell and probably this could be done way easier, but for a newbie is good enough. Feel free to come with more elegant solutions.
If you have a similar problem, here is the solution.
A code sandbox so you can play with:
https://codesandbox.io/s/epic-field-bdwyi?file=/src/index.js
import _ from "lodash";
const locs = [{ location_key: [32, 22, 11] }, { location_key: [41, 42] }];
const bulkConfigs = [
{
dataValues: {
config_key: 100
}
},
{
dataValues: {
config_key: 200
}
}
];
// map over the array of bulckConfigs and get indexes
const mergedArrays = _.map(bulkConfigs, (bulkConfig, i) => {
// create the object that we need
const objectNeed = {
// flatMap over the locs array to get flat values from objects in it
location_key: _.flatMap(locs, ({ location_key }, index) => {
// match the indexs of both arrays
if (index === i) {
// return the location_key values for each config
return location_key;
} else {
// compact to remove the undefinded values returned
return _.compact();
}
}),
config_key: bulkConfig.dataValues.config_key,
autoassign: 1
};
return objectNeed;
});
// now we just need to crate the same object as many locations and use flatMap to flatten the objects
const allObjects = _.flatMap(mergedArrays, mergedArray => {
const yy = _.map(mergedArray.location_key, location => {
const zz = {
location_key: location,
config_key: mergedArray.config_key,
autoassign: 1
};
return zz;
});
return yy;
});
console.log(allObjects);
And the more elegant version of it :)
const getConfigs = (locEl, index) => {
return _.map(locEl.location_key, (locationKey) => {
return {
location_key: locationKey,
config_key: bulkConfigs[index].dataValues.config_key,
autoassign: 1,
};
});
};
const configLocations = _.chain(locs)
.map(getConfigs)
.flatten()
.value();
console.log(configLocations);
The basic array class has .map, .forEach, .filter, and .reduce, but .groupBy i noticably absent, preventing me from doing something like
const MyComponent = (props:any) => {
return (
<div>
{
props.tags
.groupBy((t)=>t.category_name)
.map((group)=>{
[...]
})
}
</div>
)
}
I ended up implementing something myself:
class Group<T> {
key:string;
members:T[] = [];
constructor(key:string) {
this.key = key;
}
}
function groupBy<T>(list:T[], func:(x:T)=>string): Group<T>[] {
let res:Group<T>[] = [];
let group:Group<T> = null;
list.forEach((o)=>{
let groupName = func(o);
if (group === null) {
group = new Group<T>(groupName);
}
if (groupName != group.key) {
res.push(group);
group = new Group<T>(groupName);
}
group.members.push(o)
});
if (group != null) {
res.push(group);
}
return res
}
So now I can do
const MyComponent = (props:any) => {
return (
<div>
{
groupBy(props.tags, (t)=>t.category_name)
.map((group)=>{
return (
<ul key={group.key}>
<li>{group.key}</li>
<ul>
{
group.members.map((tag)=>{
return <li key={tag.id}>{tag.name}</li>
})
}
</ul>
</ul>
)
})
}
</div>
)
}
Works pretty well, but it is too bad that I need to wrap the list rather than just being able to chain method calls.
Is there a better solution?
You can use the following code to group stuff using Typescript.
const groupBy = <T, K extends keyof any>(list: T[], getKey: (item: T) => K) =>
list.reduce((previous, currentItem) => {
const group = getKey(currentItem);
if (!previous[group]) previous[group] = [];
previous[group].push(currentItem);
return previous;
}, {} as Record<K, T[]>);
// A little bit simplified version
const groupBy = <T, K extends keyof any>(arr: T[], key: (i: T) => K) =>
arr.reduce((groups, item) => {
(groups[key(item)] ||= []).push(item);
return groups;
}, {} as Record<K, T[]>);
So, if you have the following structure and array:
type Person = {
name: string;
age: number;
};
const people: Person[] = [
{
name: "Kevin R",
age: 25,
},
{
name: "Susan S",
age: 18,
},
{
name: "Julia J",
age: 18,
},
{
name: "Sarah C",
age: 25,
},
];
You can invoke it like:
const results = groupBy(people, i => i.name);
Which in this case, will give you an object with string keys, and Person[] values.
There are a few key concepts here:
1- You can use function to get the key, this way you can use TS infer capabilities to avoid having to type the generic every time you use the function.
2- By using the K extends keyof any type constraint, you're telling TS that the key being used needs to be something that can be a key string | number | symbol, that way you can use the getKey function to convert Date objects into strings for example.
3- Finally, you will be getting an object with keys of the type of the key, and values of the of the array type.
you could add the function to the array prototype in your app (note some don't recomend this: Why is extending native objects a bad practice?):
Array.prototype.groupBy = function(/* params here */) {
let array = this;
let result;
/* do more stuff here*/
return result;
};
Then create an interface in typescript like this:
.d.ts version:
interface Array<T>
{
groupBy<T>(func:(x:T) => string): Group<T>[]
}
OR in a normal ts file:
declare global {
interface Array<T>
{
groupBy<T>(func:(x:T) => string): Group<T>[]
}
}
Then you can use:
props.tags.groupBy((t)=>t.category_name)
.map((group)=>{
[...]
})
A good option might be lodash.
npm install --save lodash
npm install --save-dev #types/lodash
Just import it import * as _ from 'lodash' and use.
Example
_.groupBy(..)
_.map(..)
_.filter(..)
Instead of groupby use reduce. Suppose product is your array
let group = product.reduce((r, a) => {
console.log("a", a);
console.log('r', r);
r[a.organization] = [...r[a.organization] || [], a];
return r;
}, {});
console.log("group", group);
During the TC39 meeting of December 2021, the proposal introducing the new Array.prototype.groupBy and Array.prototype.groupByToMap function has reached stage 3 in the specification process.
https://github.com/tc39/proposal-array-grouping
https://github.com/tc39/proposals/commit/b537605f01df50fd4901be5ce4aa0d02fe6e7193
Here's how both functions are supposed to look like according to the README linked above:
const array = [1, 2, 3, 4, 5];
// groupBy groups items by arbitrary key.
// In this case, we're grouping by even/odd keys
array.groupBy((num, index, array) => {
return num % 2 === 0 ? 'even': 'odd';
});
// => { odd: [1, 3, 5], even: [2, 4] }
// groupByToMap returns items in a Map, and is useful for grouping using
// an object key.
const odd = { odd: true };
const even = { even: true };
array.groupByToMap((num, index, array) => {
return num % 2 === 0 ? even: odd;
});
// => Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
While not a 100% guaranty that it will really end up in a future version of JavaScript in the form described above (there's always a chance that the proposal can be adjusted or dropped, notably for compatibility reasons), it's nevertheless a strong commitment to have this groupBy feature offered in the standard lib soon.
By ripple effect, it also means that these functions will be also available in TypeScript.