How to fill a two-dimensional array with the specified percentage of filling?
For example, there is an array of 10 * 10, it is necessary to fill 30% of the cells in it, therefore, 30 cells will contain data, 70 will be empty.
The task arose as a result of solving another, larger problem, therefore, I publish the entire code below. Array Generator Method - generateData
I have three solutions.
The first solution is to recursively call a function that bypasses the array and gradually fills the empty cells with data. We start with the zero element of the array, if we have not reached the fill number, we call the function again. The disadvantage is that the recursive approach itself is not very good and the filled cells get stuck on one side. A possible solution is to reach the end of the array in the opposite direction.
const getStyle = style => Object.assign({}, style); const universeStyle = length => ({ position: "relative", width: `${length * 10}px`, height: `${length * 10}px`, fontSize: "0", border: "1px solid #222" }); const cellStyle = item => ({ background: `${item.color}`, display: "inline-block", boxSizing: "border-box", width: "10px", height: "10px" //border: "1px solid grey" }); const settingsStyle = () => ({ margin: "10px 0 0 0" }); const Cell = ({ item }) => ( <div x={item.x} y={item.y} style={getStyle(cellStyle(item))} /> ); class Universe extends React.Component { render() { return ( <div style={getStyle(universeStyle(this.props.width))}> {this.props.data.map(item => item.map(item => <Cell item={item} />))} </div> ); } } class Settings extends React.Component { render() { const {fill} = this.props; return ( <div style={getStyle(settingsStyle())}> <button onClick={() => this.props.generateUniverse()}>Generate</button> <div> <input onChange={event => this.props.setFill(event)} type="range" value={fill} /> <span>{fill}</span> </div> </div> ) } } class App extends React.Component { constructor(props) { super(props); this.state = { data: [], fill: 30 }; } componentDidMount() { this.generateUniverse() } getColor(colors = ['white', 'black']) { return colors[Math.floor(Math.random() * colors.length)]; } generateData(width, fill) { const count = (width * width * fill) / 100; const getColor = this.getColor; let current = 0; const spaceData = Array.from({ length: width }).map((item, y) => Array.from({ length: width }).map((item, x) => ({y: y, x: x, color: 'white'}) )) function fillData(data) { if (current < count) { data.forEach((line, y, data) => line.forEach((obj, x, line) => { if (current < count) { obj.color = getColor(); obj.color === 'black' ? current++ : null; } else { return data } }) ) return fillData(data) } return data } return fillData(spaceData) } generateUniverse() { this.setState({ data: this.generateData(this.props.width, this.state.fill) }); } redrawUniverse() { const idRedraw = setTimeout(() => { this.setState({ data: this.generateData(this.props.width) }, this.redrawUniverse); }, 500); this.idRedraw = idRedraw; } stopRedraw() { clearTimeout(this.idRedraw) } setFill(event) { console.log(event.target.value) this.setState({fill: event.target.value}) } render() { const [{width}, {data, fill}] = [this.props, this.state]; return ( <div> <Universe width={width} data={data} /> <Settings fill={fill} setFill={(event) => this.setFill(event)} stopRedraw={() => this.stopRedraw()} startRedraw={() => this.redrawUniverse()} generateUniverse={() => this.generateUniverse()} /> </div> ) } } ReactDOM.render(<App width={10} />, document.body); <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> The second solution is to randomly generate the X, Y coordinates of each point; if the required point in the array is already filled — skip, if not, fill. The process is repeated until the array reaches the fill. The disadvantage is that at large percentages of filling, we will have few empty points and the program will spend more time searching for an empty value.
const getStyle = style => Object.assign({}, style); const universeStyle = length => ({ position: "relative", width: `${length * 10}px`, height: `${length * 10}px`, fontSize: "0", border: "1px solid #222" }); const cellStyle = item => ({ background: `${item.color}`, display: "inline-block", boxSizing: "border-box", width: "10px", height: "10px" }); const settingsStyle = () => ({ margin: "10px 0 0 0" }); const Cell = ({ item }) => ( <div x={item.x} y={item.y} style={getStyle(cellStyle(item))} /> ); class Universe extends React.Component { render() { return ( <div style={getStyle(universeStyle(this.props.width))}> {this.props.data.map(item => item.map(item => <Cell item={item} />))} </div> ); } } class Settings extends React.Component { render() { const {fill} = this.props; return ( <div style={getStyle(settingsStyle())}> <button onClick={() => this.props.generateUniverse()}>Generate</button> <div> <input onChange={event => this.props.setFill(event)} type="range" value={fill} /> <span>{fill}</span> </div> </div> ) } } class App extends React.Component { constructor(props) { super(props); this.state = { data: [], fill: 30 }; } componentDidMount() { this.generateUniverse(); } getRandom(data) { return data[Math.floor(Math.random() * data.length)]; } generateData(width, fill) { const countFillObj = (width * width * fill) / 100; var currentFillObj = 0; const getRandom = this.getRandom; const arr = Array.from(Array(width).keys()); const data = Array.from({ length: width }).map((item, y) => Array.from({ length: width }).map((item, x) => ({y: y, x: x, color: 'white'}) )) if (fill === 100) { return Array.from({ length: width }).map((item, y) => Array.from({ length: width }).map((item, x) => ({y: y, x: x, color: 'black'}) )) } while (currentFillObj < countFillObj) { var [randomX, randomY] = [getRandom(arr), getRandom(arr)]; var currentObj = data[randomX][randomY]; if (currentObj.color === 'white') { currentObj.color = 'black'; currentFillObj += 1 } } return data } generateUniverse() { this.setState({ data: this.generateData(this.props.width, this.state.fill) }); } redrawUniverse() { const idRedraw = setTimeout(() => { this.setState({ data: this.generateData(this.props.width) }, this.redrawUniverse); }, 500); this.idRedraw = idRedraw; } stopRedraw() { clearTimeout(this.idRedraw) } setFill(event) { this.setState({fill: event.target.value}); this.generateUniverse(); } render() { const [{width}, {data, fill}] = [this.props, this.state]; return ( <div> <Universe width={width} data={data} /> <Settings fill={fill} setFill={(event) => this.setFill(event)} stopRedraw={() => this.stopRedraw()} startRedraw={() => this.redrawUniverse()} generateUniverse={() => this.generateUniverse()} /> </div> ) } } ReactDOM.render(<App width={10} />, document.body); <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script> The third solution appeared while writing a question, you can try to enter a correction factor in the probability of filling. And when traversing an array, when a point is filled / skipped, the probability of the next points filling / skipping changes.
Share, please, ideas
a(x,y) => b(x*n+y)and, accordingly, de-linearized. So in fact, the task is reduced to the generation of a given number (z%) of non-repeating random integers in a given range, and this is a very standard task. - Akina