Create a Hash/Associative Array/Dictionary-like object that can be initialized with key/value pairs. The object should behave like a native Hash/Associative Array/Dictionary of the language, but with the following differences:
(The value assigned to keys may be changed by normal assignment however).
If the language supportsMagic Methods, then show how these work.
map mapamapa["A"] = 65mapa["B"] = 66mapa["C"] = 67foreach valor in mapaprint valorprint mapa[valor]next valor
A65B66C67
FB doesn't have Dict natively, but we can implement them via Type
Typedictm1AsString*1m2AsIntegerEndTypeDimmapOf(1To3)Asdict=>{("A",65),("B",66),("C",67)}ForiAsInteger=1ToUbound(mapOf)PrintmapOf(i).m1PrintmapOf(i).m2Nexti
A 65B 66C 67
#include<iostream>#include<map>#include<utility>usingnamespacestd;template<typenameT>classFixedMap:privateT{// Two standard maps are used to implement FixedMap. One as a private// base class which will allow the values (but not the keys) to be modified.// Members of a private base class are not exposed to the derived class which will// prevent keys from being added or deleted. Another map will hold copies of// the initial values.Tm_defaultValues;public:FixedMap(Tmap):T(map),m_defaultValues(move(map)){}// Expose members of the base class that do not modify the map.usingT::cbegin;usingT::cend;usingT::empty;usingT::find;usingT::size;// Also expose members that can modify values but not add or remove keys.usingT::at;usingT::begin;usingT::end;// The [] operator will normally add a new key if the key is not already in the// map. Instead, throw an error if the key is missing.auto&operator[](typenameT::key_type&&key){// Make it behave like at()returnthis->at(forward<typenameT::key_type>(key));}// Instead of removing a key, change the sematics of erase() to restore// the original value of the key.voiderase(typenameT::key_type&&key){T::operator[](key)=m_defaultValues.at(key);}// Also change the sematics of clear() to restore all keysvoidclear(){// Reset the base class using the defaultsT::operator=(m_defaultValues);}};// Print the contents of a mapautoPrintMap=[](constauto&map){for(auto&[key,value]:map){cout<<"{"<<key<<" : "<<value<<"} ";}cout<<"\n\n";};intmain(void){// Create a fixed map based on the standard mapcout<<"Map intialized with values\n";FixedMap<map<string,int>>fixedMap({{"a",1},{"b",2}});PrintMap(fixedMap);cout<<"Change the values of the keys\n";fixedMap["a"]=55;fixedMap["b"]=56;PrintMap(fixedMap);cout<<"Reset the 'a' key\n";fixedMap.erase("a");PrintMap(fixedMap);cout<<"Change the values the again\n";fixedMap["a"]=88;fixedMap["b"]=99;PrintMap(fixedMap);cout<<"Reset all keys\n";fixedMap.clear();PrintMap(fixedMap);try{// Adding or retrieving a missing key is a run time errorcout<<"Try to add a new key\n";fixedMap["newKey"]=99;}catch(exception&ex){cout<<"error: "<<ex.what();}}
Map intialized with values{a : 1} {b : 2} Change the values of the keys{a : 55} {b : 56} Reset the 'a' key{a : 1} {b : 56} Change the values the again{a : 88} {b : 99} Reset all keys{a : 1} {b : 2} Try to add a new keyerror: map::at
structDefaultAA(TK,TV){TV[TK]standard,current;this(TV[TK]default_)pure/*nothrow*/@safe{this.standard=default_;this.current=default_.dup;}aliascurrentthis;voidremove(inTKkey)purenothrow{current[key]=standard[key];}voidclear()pure/*nothrow*/@safe{current=standard.dup;}}voidmain(){importstd.stdio;autod=["a":1,"b":2].DefaultAA!(string,int);d.writeln;// ["a":1, "b":2]d["a"]=55;d["b"]=66;d.writeln;// ["a":55, "b":66]d.clear;d.writeln;// ["a":1, "b":2]d["a"]=55;d["b"]=66;d["a"].writeln;// 55d.remove("a");d.writeln;// ["a":1, "b":66]}
["a":1, "b":2]["a":55, "b":66]["a":1, "b":2]55["a":1, "b":66]
Go's built-in map type is mutable and so, to complete this task, we need to create a read-only wrapper for it which doesn't permit further items to be added or existing items to be deleted though does allow them to be reset to their default value.
First create a sub-directory, romap, of the project directory and place the following package in it:
packageromaptypeRomapstruct{imapmap[byte]int}// Create new read-only wrapper for the given map.funcNew(mmap[byte]int)*Romap{ifm==nil{returnnil}return&Romap{m}}// Retrieve value for a given key, if it exists.func(rom*Romap)Get(keybyte)(int,bool){i,ok:=rom.imap[key]returni,ok}// Reset value for a given key, if it exists.func(rom*Romap)Reset(keybyte){_,ok:=rom.imap[key]ifok{rom.imap[key]=0// default value of int}}
This package can now be imported and used within the main package as follows:
packagemainimport("./romap""fmt")funcmain(){// create a normal mapm:=map[byte]int{'A':65,'B':66,'C':67}// place it in a read-only wrapper so no new item can be added or item deleted.rom:=romap.New(m)// retrieve value represented by 'C' sayi,_:=rom.Get('C')fmt.Println("'C' maps to",i)// reset this to default value (doesn't actually delete the key)rom.Reset('C')i,_=rom.Get('C')fmt.Println("'C' now maps to",i)}
'C' maps to 67'C' now maps to 0
Given a list of keys and an associated list of values, the idiomatic way of expressing this concept in J would be:
lookup=:values{~keys&i.
For example:
lookup=:1020304050{~(;:'this is a test')&i.lookup;:'a test'3040
Notes:
1) While the result can not be modified or deleted, the name used to refer to it can be made to refer to something else, and once all references are lost it will be garbage collected.
2) In the above example, we have 5 values and 4 keys. The extra value is used when no key is found. If no extra value was provided, the "key not found" case would be an error case.
3) In J, objects are always referenced, but all data is passed by value. This means that objects can never be passed to a function -- only a reference to an object (its name) can be passed. This means that objects exist only in the way things are named, in J. So for the most part, we do not call things "objects" in J, and this task has nothing to do with what are called "objects" in J. However, this does demonstrate how things are created in J -- you write their definition, and can use them and/or assign to names or inspect them or whatever else.
Java supports unmodifiable maps, sets, lists, and other more specialized unmodifiable collections. In this example, we have a unmodifiable map. We first create an ordinary map, modify as needed, then call theCollections.unmodifiableMap
. We can subsequently read the map, but modification is not permitted. The returned map will subsequently throw aUnsupportedOperationException
exception if a mutation operator is called. Several are demonstrated below.
importjava.util.Collections;importjava.util.HashMap;importjava.util.Map;// Title: Create an object/Native demonstrationpublicclassImmutableMap{publicstaticvoidmain(String[]args){Map<String,Integer>hashMap=getImmutableMap();try{hashMap.put("Test",23);}catch(UnsupportedOperationExceptione){System.out.println("ERROR: Unable to put new value.");}try{hashMap.clear();}catch(UnsupportedOperationExceptione){System.out.println("ERROR: Unable to clear map.");}try{hashMap.putIfAbsent("Test",23);}catch(UnsupportedOperationExceptione){System.out.println("ERROR: Unable to put if absent.");}for(Stringkey:hashMap.keySet()){System.out.printf("key = %s, value = %s%n",key,hashMap.get(key));}}privatestaticMap<String,Integer>getImmutableMap(){Map<String,Integer>hashMap=newHashMap<>();hashMap.put("Key 1",34);hashMap.put("Key 2",105);hashMap.put("Key 3",144);returnCollections.unmodifiableMap(hashMap);}}
{out}}
ERROR: Unable to put new value.ERROR: Unable to clear map.ERROR: Unable to put if absent.key = Key 1, value = 34key = Key 2, value = 105key = Key 3, value = 144
This is a first demonstration of the task, but only implemented the functionality, not any native behavior, eg indexing. JavaScript experts may want to replace this one.
varkeyError=newError("Invalid Key Error (FixedKeyDict)");functionFixedKeyDict(obj){varmyDefault=newObject();varmyData=newObject();for(kinobj){myDefault[k]=obj[k];myData[k]=obj[k];}vargotKey=function(k){for(kkinmyDefault){if(kk==k)returntrue;}returnfalse;};this.hasKey=gotKey;varcheckKey=function(k){if(!gotKey(k))throwkeyError;};this.getItem=function(k){checkKey(k);returnmyData[k];};this.setItem=function(k,v){checkKey(k);myData[k]=v;};this.resetItem=function(k){checkKey(k);myData[k]=myDefault[k];};this.delItem=this.resetItem;this.clear=function(){for(kinmyDefault)myData[k]=myDefault[k];};this.iterator=function(){for(kinmyDefault)yield(k);};this.clone=function(){returnnewFixedKeyDict(myDefault);}this.toStr=function(){vars="";for(keyinmyData)s=s+key+" => "+myData[key]+", ";return"FixedKeyDict{"+s+"}";};}
Test run:
constBR="<BR>\n"varpl=function(s){document.write(s+BR);};pl("<pre>");varo={foo:101,bar:102};varh=newFixedKeyDict(o);pl("Fixed Key Dict Created");pl("toString : "+h.toStr());pl("get an item: "+h.getItem("foo"));pl("check a key: "+h.hasKey("boo"));pl("ditto : "+h.hasKey("bar"));h.setItem("bar",999);pl("set an item: "+h.toStr());pl("Test iterator (or whatever)");for(kinh.iterator())pl(" "+k+" => "+h.getItem(k));varg=h.clone();pl("Clone a dict");pl(" clone : "+g.toStr());pl(" original : "+h.toStr());h.clear();pl("clear or reset the dict");pl(" : "+h.toStr());try{h.setItem("NoNewKey",666);}catch(e){pl("error test : "+e.message);}
output :
Fixed Key Dict CreatedtoString : FixedKeyDict{foo => 101, bar => 102, }get an item: 101check a key: falseditto : trueset an item: FixedKeyDict{foo => 101, bar => 999, }Test iterator (or whatever) foo => 101 bar => 999Clone a dict clone : FixedKeyDict{foo => 101, bar => 102, } original : FixedKeyDict{foo => 101, bar => 999, }clear or reset the dict : FixedKeyDict{foo => 101, bar => 102, }error test : Invalid Key Error (FixedKeyDict)
jq objects are JSON objects and can be created using JSON syntax, e.g.
{"language": "jq"}
Objects can also be created programmatically, e.g.
{"one": 1} + {"two": 2}
jq objects, however, are really just values: they are immutable, and cannot be "deleted" any more than the number 1 can be deleted.
usingBackedUpImmutablefunctiontestBackedUpImmutableDict()fibr=BackedUpImmutableDict{String,Int64}(["a"=>0,"b"=>1,"c"=>1,"d"=>2,"e"=>3,"f"=>5,"g"=>8,"h"=>13,"i"=>21,"j"=>34,"extra"=>-1])x=fibr["extra"]@testx==-1fibr["extra"]=0y=fibr["extra"]@testy==0restore!(fibr,"extra")z=fibr["extra"]@testz==-1@test_throwsStringbeginfibr["k"]=55endfibr["a"]=9fibr["b"]=7# test restore all to defaultrestoreall!(fibr)@testfibr["a"]==0end
All tests pass.
// version 1.1.2funmain(args:Array<String>){// This line creates a read-only map which cannot be changed in any way nor clearedvalmap=mapOf('A'to65,'B'to66,'C'to67)println(map)}
{A=65, B=66, C=67}
Module CheckIt { Class LockedHash { Private: inventory Vars ' no same keys unlock module nosuchvariable { Error "No such value:"+letter$ } module NoNewItem { Error "No new item, use unlock method before" } module NoRemoveItem { Error "Can't remove item, use unlock method before" } Public: module Unlock { .unlock<=True } module Writeln { m=each(.Vars) while m { Print Quote$(Eval$(m, m^));",";Eval(m), } Print } Value (st$){ st$=Ucase$(st$) if exist(.Vars, st$) then =Eval(.Vars) : Exit .nosuchvariable st$ } Set (st$){ st$=Ucase$(st$) Read val if exist(.Vars, st$) then Return .Vars, st$:=val : Exit If .unlock then { Append .Vars, st$:=val} Else .NoNewItem } module Remove (st$) { if not .unlock then .NoRemoveItem st$=Ucase$(st$) Try { delete .Vars, st$ } } module Clear { Clear .Vars } Class: ' this part exist only at construction module LockedHash { While match("SN") { read st$, val st$=ucase$(st$) \\ if we append key which exist we get error Append .Vars, st$:=val } } } d=LockedHash("a", 1, "b", 2) d.writeln d("a")=55 : d("b")=66 d.writeln d.clear d.writeln d.unlock d("a")=55 : d("b")=66 Print d("a")=55, d("a")/d("b")<1 d.remove "a" d.writeln}Checkit
a[1]="Do not modify after creation";a[2]="Native demonstration";Protect[a];
Example usage:
a[3] = 2->Set::write: Tag a in a[1] is Protected. >>
We leverage native stdlib table as our own object by implementing limited actual native table functionalities.
importtables,optionstypeMyTable=objecttable:TableRef[string,int]# return empty if the key is not availableproc`[]`(m:MyTable,key:string):Option[int]=ifkeyinm.table:result=somem.table[key]else:result=noneint# update an item, doing nothing if the key is available during first initializationproc`[]=`(m:varMyTable,key:string,val:int)=ifkeynotinm.table:returnm.table[key]=valprocreset(m:varMyTable)=for_, v in m.table.mpairs: v = 0# sugar for defining MyTable objectproctoTable(vals:openarray[(string,int)]):MyTable=result.table=newTablevalsprocmain=# MyTable constructionvarmyobj={"key1":1,"key2":2,"key3":3}.toTable# test getting existing keyletval1=myobj["key1"]ifval1.isSome:echo"val1: ",val1.get# test adding new keymyobj["key4"]=4letval4=myobj["key4"]ifval4.isSome:echoval4.getelse:echo"val4 is empty"# test reset and test whether its value is zero-edresetmyobjdoAssertmyobj["key3"].get==0main()
val1: 1val4 is empty
usestrict;packageLockedHash;useparent'Tie::Hash';useCarp;subTIEHASH{my$cls=shift;my%h=@_;bless\%h,ref$cls||$cls;}subSTORE{my($self,$k,$v)=@_;croak"Can't add key $k"unlessexists$self->{$k};$self->{$k}=$v;}subFETCH{my($self,$k)=@_;croak"No key $k"unlessexists$self->{$k};$self->{$k};}subDELETE{my($self,$k)=@_;croak"No key $k"unlessexists$self->{$k};$self->{$k}=0;}subCLEAR{}# ignoredsubEXISTS{existsshift->{+shift}}subFIRSTKEY{my$self=shift;keys%$self;each%$self;}subNEXTKEY{my$self=shift;each%$self;}sublock_hash:prototype(\%) {my$ref=shift;tie(%$ref,__PACKAGE__,%$ref);}1;my%h=(a=>3,b=>4,c=>5);# lock down %hLockedHash::lock_hash(%h);# show hash content and iterationfor(sortkeys%h){print"$_ => $h{$_}\n";}# try delete bdelete$h{b};print"\nafter deleting b: b => $h{b}\n";# change value of a$h{a}=100;print"\na => $h{a}\n";# add a new key x: will dieeval{$h{x}=1};if($@){print"Operation error: $@"}
output:
a => 3b => 4c => 5after deleting b: b => 0a => 100operation error: Can't add key x at test.pl line 14 LockedHash::STORE('LockedHash=HASH(0x8cebe14)', 'x', 1) called at test.pl line 66 eval {...} called at test.pl line 66
There is no native "read-only" setting on phix dictionaries, so the following wraps a pair of them toprovide the requested functionality.
withjavascript_semanticsenumSTD,CURsequencefkds={}-- fixed key dictionaries ;-)integerfreelist=0procedurefkd_destroy(integerid)integer{std,cur}=fkds[id]destroy_dict(std)destroy_dict(cur)fkds[id]=freelistfreelist=idendprocedurefunctionfkd_new(sequencekey_pairs)integerstd=new_dict(key_pairs),cur=new_dict(std),id=freelistifid=0thenfkds=append(fkds,{std,cur})id=length(fkds)elsefreelist=fkds[id]fkds[id]={std,cur}endifreturnidendfunctionprocedurefkd_clear(integerid)integer{std,cur}=fkds[id]destroy_dict(cur)fkds[id][CUR]=new_dict(std)endprocedurefunctionfkd_get(integerid,objectkey)returngetd(key,fkds[id][CUR])endfunctionprocedurefkd_set(integerid,objectkey,data)integernode=getd_index(key,fkds[id][CUR])ifnode=NULLthenthrow("invalid/new key")endifsetd(key,data,fkds[id][CUR])endprocedureprocedurefkd_remove(integerid,objectkey)integer{std,cur}=fkds[id],node=getd_index(key,std)ifnode=NULLthenthrow("invalid key")endifsetd(key,getd_by_index(node,std),cur)endprocedurefunctionfkd_sprint(integerid)integercur=fkds[id][CUR]sequenceres=getd_all_keys(cur)fori=1tolength(res)doobjectri=res[i]res[i]={ri,getd(ri,cur)}endforreturnresendfunctionproceduremain()integerid=fkd_new({{"a",1},{"b",2}})?fkd_sprint(id)-- {{"a",1},{"b",2}}fkd_set(id,"a",55)fkd_set(id,"b",66)?fkd_sprint(id)-- {{"a",55},{"b",66}}fkd_clear(id)?fkd_sprint(id)-- {{"a",1},{"b",2}}fkd_set(id,"a",55)fkd_set(id,"b",66)?fkd_get(id,"a")-- 55fkd_remove(id,"a")tryfkd_set(id,"NoNewKey",77)catche?e[E_USER]-- "invalid/new key"endtry?fkd_sprint(id)-- {{"a",1},{"b",66}}fkd_destroy(id)endproceduremain()
fromcollectionsimportUserDictimportcopyclassDict(UserDict):''' >>> d = Dict(a=1, b=2) >>> d Dict({'a': 1, 'b': 2}) >>> d['a'] = 55; d['b'] = 66 >>> d Dict({'a': 55, 'b': 66}) >>> d.clear() >>> d Dict({'a': 1, 'b': 2}) >>> d['a'] = 55; d['b'] = 66 >>> d['a'] 55 >>> del d['a'] >>> d Dict({'a': 1, 'b': 66}) '''def__init__(self,dict=None,**kwargs):self.__init=Truesuper().__init__(dict,**kwargs)self.default=copy.deepcopy(self.data)self.__init=Falsedef__delitem__(self,key):ifkeyinself.default:self.data[key]=self.default[key]else:raiseNotImplementedErrordef__setitem__(self,key,item):ifself.__init:super().__setitem__(key,item)elifkeyinself.data:self.data[key]=itemelse:raiseKeyErrordef__repr__(self):return"%s(%s)"%(type(self).__name__,super().__repr__())deffromkeys(cls,iterable,value=None):ifself.__init:super().fromkeys(cls,iterable,value)else:forkeyiniterable:ifkeyinself.data:self.data[key]=valueelse:raiseKeyErrordefclear(self):self.data.update(copy.deepcopy(self.default))defpop(self,key,default=None):raiseNotImplementedErrordefpopitem(self):raiseNotImplementedErrordefupdate(self,E,**F):ifself.__init:super().update(E,**F)else:haskeys=Falsetry:keys=E.keys()haskeys=TureexceptAttributeError:passifhaskeys:forkeyinkeys:self[key]=E[key]else:forkey,valinE:self[key]=valforkeyinF:self[key]=F[key]defsetdefault(self,key,default=None):ifkeynotinself.data:raiseKeyErrorelse:returnsuper().setdefault(key,default)
This task is implemented as a new fenced-hash time with an interface similar to the native hash. Also it can be used a native dict.
Implementation of functions that handle fenced-hash:
;(struct fenced-hash (actual original) ...)(define(fenced-hash-refdictkey[default(lambda()(error"key not found"key))])(hash-ref(fenced-hash-actualdict)keydefault))(define(fenced-hash-set!dictkeyval)(unless(hash-has-key?(fenced-hash-actualdict)key)(error"unable to add key"key))(hash-set!(fenced-hash-actualdict)keyval))(define(fenced-hash-remove!dictkey);reset the value!(unless(hash-has-key?(fenced-hash-actualdict)key)(error"key not found"key))(hash-set!(fenced-hash-actualdict)key(hash-ref(fenced-hash-originaldict)key)))(define(fenced-hash-clear!dict);reset all values!(hash-for-each(fenced-hash-originaldict)(lambda(keyval)(hash-set!(fenced-hash-actualdict)keyval))))(define(fenced-hash-has-key?dictkey)(hash-has-key?(fenced-hash-actualdict)key))(define(fenced-hash-countdict)(hash-count(fenced-hash-actualdict)))(define(fenced-hash-iterate-firstdict)(hash-iterate-first(fenced-hash-actualdict)))(define(fenced-hash-iterate-nextdictpos)(hash-iterate-next(fenced-hash-actualdict)pos))(define(fenced-hash-iterate-keydictpos)(hash-iterate-key(fenced-hash-actualdict)pos))(define(fenced-hash-iterate-valuedictpos)(hash-iterate-value(fenced-hash-actualdict)pos))(define(*fenced-hash-printdictportmode);private custom-write ;mode is ignored(write-string"#fenced-hash"port)(write(hash->list(fenced-hash-actualdict))port))
Definition of the actual structure and a “public” creator:
(structfenced-hash(actualoriginal)#:extra-constructor-name*fenced-hash;private constructor#:omit-define-syntaxes;not sure this is a good idea#:methodsgen:custom-write[(definewrite-proc*fenced-hash-print)]#:methodsgen:dict[(definedict-reffenced-hash-ref)(definedict-set!fenced-hash-set!)(definedict-remove!fenced-hash-remove!)(definedict-has-key?fenced-hash-has-key?);unused in 5.6.3(definedict-countfenced-hash-count)(definedict-iterate-firstfenced-hash-iterate-first)(definedict-iterate-nextfenced-hash-iterate-next)(definedict-iterate-keyfenced-hash-iterate-key)(definedict-iterate-valuefenced-hash-iterate-value)])(define(fenced-hash.args); public constructor(defineoriginal(applyhashargs))(*fenced-hash(hash-copyoriginal)original))
Example: Use the fenced-hash functions:
(defined(fenced-hash"a"1"b"2))(displaylnd)(fenced-hash-set!d"a"55)(fenced-hash-set!d"b"66)(displaylnd)(fenced-hash-clear!d)(displaylnd)(fenced-hash-set!d"a"55)(fenced-hash-set!d"b"66)(displaylnd)(fenced-hash-remove!d"a")(displaylnd)
#fenced-hash(("b" . 2) ("a" . 1))#fenced-hash(("b" . 66) ("a" . 55))#fenced-hash(("b" . 2) ("a" . 1))#fenced-hash(("b" . 66) ("a" . 55))#fenced-hash(("b" . 66) ("a" . 1))
Example (continued): Use the same object as a dict. The dict-clear! method is not defined, so we must call fenced-hash-clear! instead.
(fenced-hash-clear!d)(displaylnd)(dict-set!d"a"55)(dict-set!d"b"66)(displaylnd)(fenced-hash-clear!d);dict-clear is not defined(displaylnd)(dict-set!d"a"55)(dict-set!d"b"66)(displaylnd)(dict-remove!d"a")(displaylnd)
#fenced-hash(("b" . 2) ("a" . 1))#fenced-hash(("b" . 66) ("a" . 55))#fenced-hash(("b" . 2) ("a" . 1))#fenced-hash(("b" . 66) ("a" . 55))#fenced-hash(("b" . 66) ("a" . 1))
(formerly Perl 6)
Here we use delegation to handle all the normal hash methods that we don't need to override to define our new class.
classFixedHash {has$.hashhandles *;methodnew(*@args) {self.bless:hash =>Hash.new:@args }methodAT-KEY(FixedHash:D:$keyiscopy)isrw {$!hash.EXISTS-KEY($key) ??$!hash.AT-KEY($key) !!Failure.new(q{can't store value for unknown key}); }methodDELETE-KEY($key) {$!hash.{$key} =Nil }}# Testingmy$fh =FixedHash.new:"a" =>1,"b" =>2;say$fh<a b>;# 1 2$fh<b>:delete;say$fh<a b>;# 1 Nil$fh<b> =42;say$fh<a b>;# 1 42say$fh<c>;# Nil$fh<c> =43;# error
(1 2)(1 (Any))(1 42)can't store value for unknown key in block <unit> at native-demonstration.p6:17Actually thrown at: in block <unit> at native-demonstration.p6:17
By definingFALLBACK any class can handle undefined method calls. Since any class inherits plenty of methods fromAny our magic object will be more of a novice conjurer then a master wizard proper.
classMagic {has%.hash;multimethodFALLBACK($name, |c)isrw {# this will eat any extra parameters%.hash{$name} }multimethodFALLBACK($name)isrw {%.hash{$name} }}my$magic =Magic.new;$magic.foo =10;say$magic.foo;$magic.defined =False;# error
10Cannot modify an immutable Bool in block <unit> at native-demonstration.p6:15
# Project : Create an object/Native demonstrationmap = []map["A"] = 65map["B"] = 66map["C"] = 67see map + nl
Output:
A65B66C67
# A FencedHash acts like a Hash, but with a fence around its keys.# One may change its values, but not its keys. Any attempt to insert# a new key raises KeyError. One may delete a key, but this only# restores its original value.## FencedHash reimplements these Hash methods: #[] #[]= #clear #delete# #delete_if #default #default= #each_key #each_pair #each_value# #fetch #has_key? #keep_if #keys #length #values #values_atclassFencedHash# call-seq:# FencedHash.new(hash, obj=nil) -> fh## Creates a FencedHash that takes its keys and original values from# a source _hash_. The source _hash_ can be any object that# responds to each_pair. Sets the default value for missing keys to# _obj_, so FencedHash#[] returns _obj_ when a key is not in fence.definitialize(hash,obj=nil)@default=obj@hash={}hash.each_pairdo|key,value|# @hash[key][0] = current value# @hash[key][1] = original value@hash[key]=[value,value]endenddefinitialize_clone(orig)# Object#clone calls here in Ruby 2.0. If _orig_ was frozen, then# each array of _values_ is frozen, so make frozen clones.supercopy={}@hash.each_pair{|key,values|copy[key]=values.clone}@hash=copyenddefinitialize_dup(orig)# Object#dup calls here in Ruby 2.0. If _orig_ was frozen, then# make duplicates that are not frozen.supercopy={}@hash.each_pair{|key,values|copy[key]=values.dup}@hash=copyend# Retrieves current value for _key_, like Hash#[]. If _key_ is not# in fence, returns default object.def[](key)values=@hash[key]ifvaluesvalues[0]else@defaultendend# call-seq:# fh[key] = value -> value# fh.store(key, value) -> value## Sets _value_ for a _key_. Returns _value. If _key_ is not in# fence, raises KeyError.def[]=(key,value)values=@hash[key]ifvaluesvalues[0]=valueelseraiseKeyError,"fence prevents adding new key:#{key.inspect}"endendaliasstore[]=# Resets all keys to their original values. Returns self.defclear@hash.each_value{|values|values[0]=values[1]}selfend# Resets _key_ to its original value. Returns old value before# reset. If _key_ is not in fence, returns +nil+.defdelete(key)values=@hash[key]ifvaluesold=values[0]values[0]=values[1]old# return oldend# else return nilend# call-seq:# fh.delete_if {|key, value| block } -> fh# fh.delete_if -> enumerator## Yields each _key_ with current _value_ to _block_. Resets _key_# to its original value when block evaluates to true.defdelete_ififblock_given?@hash.each_pairdo|key,values|yield(key,values[0])andvalues[0]=values[1]endselfelseenum_for(:delete_if){@hash.size}endend# The default value for keys not in fence.attr_accessor:default# call-seq:# fh.each_key {|key| block} -> fh# fh.each_key -> enumerator## Yields each key in fence to the block.defeach_key(&block)ifblock@hash.each_key(&block)selfelseenum_for(:each_key){@hash.size}endend# call-seq:# fh.each_pair {|key, value| block} -> fh# fh.each_pair -> enumerator## Yields each key-value pair to the block, like Hash#each_pair.# This yields each [key, value] as an array of 2 elements.defeach_pairifblock_given?@hash.each_pair{|key,values|yield[key,values[0]]}selfelseenum_for(:each_pair){@hash.size}endend# call-seq# fh.each_value {|value| block} -> fh# fh.each_value -> enumerator## Yields current value of each key-value pair to the block.defeach_valueifblock_given?@hash.each_value{|values|yieldvalues[0]}elseenum_for(:each_value){@hash.size}endend# call-seq:# fenhsh.fetch(key [,default])# fenhsh.fetch(key) {|key| block }## Fetches value for _key_. Takes same arguments as Hash#fetch.deffetch(*argv)argc=argv.lengthunlessargc.between?(1,2)raise(ArgumentError,"wrong number of arguments (#{argc} for 1..2)")endifargc==2andblock_given?warn("#{caller[0]}: warning: "+"block supersedes default value argument")endkey,default=argvvalues=@hash[key]ifvaluesvalues[0]elsifblock_given?yieldkeyelsifargc==2defaultelseraiseKeyError,"key not found:#{key.inspect}"endend# Freezes this FencedHash.deffreeze@hash.each_value{|values|values.freeze}superend# Returns true if _key_ is in fence.defhas_key?(key)@hash.has_key?(key)endaliasinclude?has_key?aliasmember?has_key?# call-seq:# fh.keep_if {|key, value| block } -> fh# fh.keep_if -> enumerator## Yields each _key_ with current _value_ to _block_. Resets _key_# to its original value when block evaluates to false.defkeep_ififblock_given?@hash.each_pairdo|key,values|yield(key,values[0])orvalues[0]=values[1]endselfelseenum_for(:keep_if){@hash.size}endend# Returns array of keys in fence.defkeys@hash.keysend# Returns number of key-value pairs.deflength@hash.lengthendaliassizelength# Converts self to a regular Hash.defto_hresult=Hash.new(@default)@hash.each_pair{|key,values|result[key]=values[0]}resultend# Converts self to a String.defto_s"#<#{self.class}:#{to_h}>"endaliasinspectto_s# Returns array of current values.defvalues@hash.each_value.map{|values|values[0]}end# Returns array of current values for keys, like Hash#values_at.defvalues_at(*keys)keys.map{|key|self[key]}endend
Best seen running in your browser either byScalaFiddle (ES aka JavaScript, non JVM) orScastie (remote JVM).
objectCreateMapObjectextendsApp{valmap=Map('A'->65,'B'->66,'C'->67)println(map)}
This solution uses a dict(ionary), so requires Tcl 8.5 or better. Variable traces are used to detect write or unset access to such a protected variable, restore it to the backup value at protection time, and throw an exception
procprotect_var{upvar1$_varvartraceaddvariablevar{writeunset}[listprotect0$var]}procprotect0{backupname1name2op}{upvar1$name1vartraceremovevariablevar{writeunset}[listprotect0$backup]setvar$backuptraceaddvariablevar{writeunset}[listprotect0$backup]return-codeerror"$name1 is protected"}proctryingcmd{#-- convenience function for demoputs"trying: $cmd"if[catch{uplevel1$cmd}msg]{puts$msg}}
Testing:
dict set dic 1 one dict set dic 2 twoputs dic:$dicprotect dictrying "dict set dic 3 three"puts dic:$dictrying "dict unset dic 1"trying "unset dic"puts dic:$dic
displays on stdout:
dic:1 one 2 twotrying: dict set dic 3 threecan't set "dic": dic is protecteddic:1 one 2 twotrying: dict unset dic 1can't set "dic": dic is protectedtrying: unset dicdic:1 one 2 two
1) Vlang embraces immutability by default.
2) Variables, function args, structs, and string values are immutable by default.
3) An immutable hash map would not allow new keys, to be changed, or to be removed.
4) To make mutable and allow changes, in Vlang, the keyword 'mut' is used.
muthma:=map[string]int{}// mutable via using "mut"hma["A"]=40hma["B"]=41hma["C"]=42println(hma)println(hma["F"])// non-existent keys return 0 by defaulthma.delete("C")// deletion of key possible, as was declared mutable ("mut")println(hma)hmb:={"D":50,"E":51,"F":52}// immutable hash mapprintln(hmb)hmb.delete("F")// attempting to delete an immutable key will cause an errorhmb["D"]=60// attempting to change an immutable value will also cause an error
classFixedSizeMap{constructnew(map){// copy the map so it cannot be mutated from the original reference_map={}for(meinmap.toList)_map[me.key]=me.value}containsKey(key){_map[key]!=null}count{_map.count}keys{_map.keys}values{_map.values}[key]{_map[key]}[key]=(value){// do nothing if key doesn't already existif(_map[key]!=null)_map[key]=value}reset(key){vart=_map[key].type// leave unaltered if no suitable default value_map[key]=(t==Num)?0:(t==String)?"":(t==Bool)?false:(t==List)?[]:(t==Map)?{}:_map[key]}iterate(iterator){_map.iterate(iterator)}iteratorValue(iterator){_map.iteratorValue(iterator)}toString{_map.toString}}varmap={"a":1,"b":2}varfsm=FixedSizeMap.new(map)System.print(fsm)System.print(fsm.count)fsm["a"]=3fsm["b"]=4System.print(fsm)System.print(fsm.containsKey("c"))fsm["c"]=5// attempt to add a new key/value pairSystem.print(fsm)// ignoredfsm.reset("a")System.print(fsm)System.print(fsm.keys.toList)System.print(fsm.values.toList)for(meinfsm)System.print([me.key,me.value])
{b: 2, a: 1}2{b: 4, a: 3}false{b: 4, a: 3}{b: 4, a: 0}[b, a][4, 0][b, 4][a, 0]
zkl has two dictionary objects: SD, a small dictionary that is created immutable and the "regular" dictionary has has a makeReadOnly method. They both behave the same when locked down.
d:=SD("one",1,"two",2);d.keys; //-->L("one","two")d["one"]; //-->1d.add("three",3); // error thrownd.pop("one") // error thrown